Compare commits

...

89 Commits

Author SHA1 Message Date
agapple
d53bfd7ee7 [maven-release-plugin] prepare release canal-1.1.4 2019-09-02 14:24:03 +08:00
agapple
ccc45b672c fixed issue #2127 , remove logback stdout 2019-09-02 14:10:29 +08:00
agapple
0d42bdafe2 fixed stdout 2019-09-02 14:04:19 +08:00
agapple
95050ff382 fixed test example 2019-09-02 13:46:35 +08:00
agapple
5addd246ac fixed unused error 2019-09-01 11:20:00 +08:00
agapple
cfc95e2b7a fixed docker config 2019-09-01 10:41:00 +08:00
agapple
44187d9e3a fixed docker config 2019-09-01 10:15:58 +08:00
agapple
c76e8ba0e4 fixed canal-admin 2019-08-31 15:23:58 +08:00
rewerma
1bcae9dc49 template config load (#2123)
* template config load

* compile vue frontend for product
2019-08-31 14:56:28 +08:00
agapple
3a388308d9 fixed compiler error 2019-08-31 13:01:52 +08:00
agapple
a60ec6fa42 fixed tescase failed 2019-08-31 13:00:31 +08:00
agapple
456945fe3a add autoRegister and cluster 2019-08-31 12:59:12 +08:00
agapple
ba24c15833 add conflict file 2019-08-31 12:47:27 +08:00
agapple
4e0b8a0278 fixed admin 2019-08-31 12:24:41 +08:00
rewerma
274e91f8aa bug fix (#2122)
* admin端口调整完成

* server端适配调整

* 调整部分功能

* 单机/集群admin调试完成

* 重新发布前端页面

* bug fix

* 解决server端admin配置会被远程覆盖的问题
解决default-instance.xml无法加载远程zkServers配置的问题

* 集群模式下取活动实例的问题
2019-08-31 12:22:16 +08:00
rewerma
89055f7405 canal-admin完善 (#2121)
* admin端口调整完成

* server端适配调整

* 调整部分功能

* 单机/集群admin调试完成

* 重新发布前端页面
2019-08-30 18:09:22 +08:00
rewerma
e8dba41216 code optimize (#2108)
* code optimize
2019-08-27 22:52:14 +08:00
agapple
f52b2cccd5 fixed testcase 2019-08-27 22:45:27 +08:00
rewerma
7386327af9 code optimize (#2107)
* admin assembly打包

* 修改npm  profile

* code optimize

* code optimize
2019-08-27 22:24:53 +08:00
rewerma
c48407b449 修改npm profile (#2106)
* admin assembly打包

* 修改npm  profile
2019-08-27 20:55:15 +08:00
rewerma
d92badeb45 admin assembly打包 (#2104) 2019-08-27 19:48:22 +08:00
agapple
7093179cc8 fixed issue #2099 , rename poll to polling 2019-08-27 16:25:42 +08:00
agapple
bf89974f7c fixed issue #2099 , support remote config poll 2019-08-27 16:11:20 +08:00
rewerma
2e1b7a5093 通过http从canal-admin中拉配置 (#2093)
* modify

* http拉取配置监听

* revert main pom

* 整理代码
2019-08-26 19:01:08 +08:00
agapple
3d4dbc9a8c fixed issue #2099 , support user/passwd acl and canal admin operator 2019-08-26 18:16:33 +08:00
agapple
e376a129ed fixed canal-admin vue 2019-08-24 16:54:27 +08:00
wangwengeek
983ff5fc73 查询数据过长变成byte数组转化为json解决 (#2090)
Caused by: com.alibaba.fastjson.JSONException: syntax error, pos 2, json : [B@406b7f2 #2089
2019-08-23 17:38:22 +08:00
agapple
3a74f6124a fixed issue #1940 #2088 , add canal.register.ip support docker 2019-08-22 23:48:19 +08:00
agapple
bd974e3da8 upgrade fastjson 1.2.58 2019-08-22 23:03:22 +08:00
agapple
86cd805a64 upgrade fastsql 973 2019-08-22 22:02:58 +08:00
agapple
8b54b9e596 add python client info 2019-08-22 15:17:33 +08:00
agapple
2d3baeab7d fastsql tsdb testcase 2019-08-22 15:08:36 +08:00
黄大海
7b245c5a64 fix #1762 (#1959) 2019-08-21 14:33:31 +08:00
agapple
78af6b1130 fixed issue #2067 , add example 2019-08-21 13:52:34 +08:00
ever
7547cac3c5 fix compiler problem: upgrade rocketmq to 4.5.2 (#2074) 2019-08-21 12:48:46 +08:00
soocold
066743cfd6 always check whether ringbuffer has an exception in event processor (#2053) 2019-08-21 12:46:17 +08:00
rewerma
fdd0763567 新增es adapter rest client和认证机制 (#2046)
* 新增es adapter rest client和认证机制

* 修改getMapping位置

* 代码调整

* 代码调整

* 整理代码

* fix #2048
2019-08-21 12:44:57 +08:00
ThomasPerkins1123
bcdbf75dc5 Add unit tests for com.alibaba.otter.canal.parse.driver.mysql.utils.ByteHelper (#2039)
These tests were written using Diffblue Cover.
2019-08-21 12:44:24 +08:00
jason-liew
db23f15b61 fix: hbase etl sql syntax error in some case (#2034) 2019-08-21 12:44:04 +08:00
rewerma
44722fe01c canal-admin-ui 指定profile编译 (#2019)
* canal-admin-ui 指定profile编译

* 启动实例时选择指定节点

* modify

* modify

* 静态页面修改
2019-07-22 09:15:14 +08:00
bucketli
be9813142f remove openjdk12 2019-07-20 19:05:49 +08:00
bucketli
c48e80a163 remove oraclejdk8 CI config,add oraclejdk12 and openjdk12 2019-07-20 18:34:01 +08:00
bucketli
c1bfad4f2d 1.not compile canal-admin module,unless you need it, to be optimize.#1998,2.support JDK 11 & 12 ,3.fix the CI block test 2019-07-20 18:12:07 +08:00
rewerma
10f4ef6acd canal admin (#1998)
* 增加注解

* modify

* 文件大小写问题
2019-07-16 21:47:53 +08:00
Li Qiang
4c0d82fb98 Update README.md 2019-07-16 20:35:37 +08:00
rewerma
7cec0994e9 Merge branch 'master' into master 2019-07-14 19:24:42 +08:00
mcy
483044916d canal admin 2019-07-14 19:18:15 +08:00
mcy
c8bb6e200c modify 2019-07-14 09:59:58 +08:00
mcy
9cc6b67426 ddl sql 2019-07-14 09:59:35 +08:00
mcy
62072ea9ab modify 2019-07-14 00:06:25 +08:00
mcy
f3daceea6b canal instance manager module finished 2019-07-13 21:34:44 +08:00
rewerma
4efc0f6078 modify 2019-07-13 18:03:28 +08:00
rewerma
40d2a8c6cc modify 2019-07-13 14:33:42 +08:00
rewerma
28da536ec8 modify 2019-07-13 11:47:55 +08:00
mcy
6f129bf524 modify 2019-07-12 21:00:33 +08:00
mcy
31ae308760 增减jmx状态控制 2019-07-12 20:59:20 +08:00
mcy
c2ce662f9a modify 2019-07-12 20:08:16 +08:00
mcy
7044e172f5 jmx 客户端 2019-07-12 20:06:58 +08:00
Menng
c070757da0 修正文档链接&优化格式 #1965 (#1967) 2019-07-12 10:13:29 +08:00
xingwenge
92dd2bb189 Update README.md (#1981) 2019-07-12 09:53:06 +08:00
rewerma
8639b45fe9 fix #1761 (#1973)
* Message转Dml对null值的判断

* Message转Dml对null值的判断

* Message转Dml对null值的判断

* fix #1761
2019-07-12 09:52:36 +08:00
mcy
6b9487a0a0 modify 2019-07-12 09:16:10 +08:00
rewerma
5741f5a347 modify 2019-07-12 00:42:18 +08:00
rewerma
4c3ef6772c modify 2019-07-12 00:01:37 +08:00
rewerma
64824bc5aa modify 2019-07-11 23:29:49 +08:00
mcy
23209f24ed 增减jmx状态控制 2019-07-11 21:34:12 +08:00
mcy
015026ea91 modify 2019-07-11 21:31:38 +08:00
mcy
c7967e989c modify 2019-07-11 20:46:02 +08:00
mcy
e4b37f33de modify 2019-07-11 20:44:39 +08:00
mcy
598f15a404 modify 2019-07-11 16:30:50 +08:00
mcy
ec5f1eb10d modify 2019-07-11 16:16:32 +08:00
mcy
28c0aac98d modify 2019-07-11 15:11:29 +08:00
mcy
50ea6870dc modify 2019-07-11 15:07:57 +08:00
mcy
317439670c modify 2019-07-11 10:16:12 +08:00
rewerma
64ff140304 modify 2019-07-11 01:05:11 +08:00
rewerma
a93af74539 modify 2019-07-11 00:20:56 +08:00
rewerma
fc39b2af68 modify 2019-07-10 21:46:38 +08:00
mcy
e87a0c3b63 canal-admin init 2019-07-10 16:22:38 +08:00
mcy
b43043f4e4 Merge remote-tracking branch 'origin/master' 2019-07-09 19:09:24 +08:00
mcy
60a46ae941 fix #1761 2019-07-09 19:08:35 +08:00
Braavos
b5c05c2fd2 Add unit tests for com.alibaba.otter.canal.parse.driver.mysql.utils.MySQLPasswordEncrypter (#1937)
These tests were written using Diffblue Cover.
2019-06-28 14:57:18 +08:00
靳光宇
0cf18976d9 Update HbaseSyncService.java (#1936)
Fix batch update or delete with only affects the first data issue
2019-06-28 14:56:51 +08:00
Li Qiang
803c0607d3 Update README.md 2019-06-27 19:45:26 +08:00
Li Qiang
f04e742f4f #1897 , fix DummyEventStore implements new method (#1932) 2019-06-27 09:58:24 +08:00
cjk87927
c81034dda3 增加表字段过滤功能 (#1914)
* 增加表字段过滤功能

* 增加表字段过滤功能

* 更换fastsql版本通过编译

* 1.修改字段配置格式 2.增加字段黑名单配置(冲突时以白名单为准)
2019-06-26 17:45:14 +08:00
rewerma
24f0eece42 Merge pull request #1 from alibaba/master
merge
2019-06-19 21:00:31 +08:00
mcy
4f7842c843 Message转Dml对null值的判断 2019-05-14 17:38:01 +08:00
mcy
b870e7ecd0 Message转Dml对null值的判断 2019-05-14 17:35:34 +08:00
mcy
331dd63c5f Message转Dml对null值的判断 2019-05-14 17:31:51 +08:00
595 changed files with 42718 additions and 1626 deletions

5
.gitignore vendored
View File

@@ -18,3 +18,8 @@ jtester.properties
*.rpm
client-adapter/example/
*.dat
conf/
canal-admin/canal-admin-ui/dist/
canal-admin/canal-admin-ui/node/
canal-admin/canal-admin-ui/node_modules/
canal-admin/canal-admin-server/src/main/resources/conf/

View File

@@ -2,10 +2,10 @@ language: java
sudo: false # faster builds
jdk:
- oraclejdk12
- openjdk11
- oraclejdk11
- openjdk8
- oraclejdk8
cache:
directories:

195
README.md
View File

@@ -1,133 +1,100 @@
<div class="blog_content">
<div class="iteye-blog-content-contain">
[![Build Status](https://travis-ci.com/alibaba/canal.svg?branch=master)](https://travis-ci.com/alibaba/canal)
[![build status](https://travis-ci.com/alibaba/canal.svg?branch=master)](https://travis-ci.com/alibaba/canal)
[![codecov](https://codecov.io/gh/alibaba/canal/branch/master/graph/badge.svg)](https://codecov.io/gh/alibaba/canal)
![maven](https://img.shields.io/maven-central/v/com.alibaba.otter/canal.svg)
![license](https://img.shields.io/github/license/alibaba/canal.svg)
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/alibaba/canal.svg)](http://isitmaintained.com/project/alibaba/canal "Average time to resolve an issue")
[![Percentage of issues still open](http://isitmaintained.com/badge/open/alibaba/canal.svg)](http://isitmaintained.com/project/alibaba/canal "Percentage of issues still open")
[![average time to resolve an issue](http://isitmaintained.com/badge/resolution/alibaba/canal.svg)](http://isitmaintained.com/project/alibaba/canal "average time to resolve an issue")
[![percentage of issues still open](http://isitmaintained.com/badge/open/alibaba/canal.svg)](http://isitmaintained.com/project/alibaba/canal "percentage of issues still open")
<h1>背景</h1>
<p style="font-size: 14px;"> 早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求。不过早期的数据库同步业务,主要是基于trigger的方式获取增量变更,不过从2010年开始,阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&amp;消费的业务,从此开启了一段新纪元。</p>
<p style="font-size: 14px;"> ps. 目前内部版本已经支持mysql和oracle部分版本的日志解析,当前的canal开源版本支持5.7及以下的版本(阿里内部mysql 5.7.13, 5.6.10, mysql 5.5.18和5.1.40/48)</p>
<p style="font-size: 14px;"> </p>
<p style="font-size: 14px;">基于日志增量订阅&amp;消费支持的业务:</p>
<ol style="font-size: 14px;">
<li>数据库镜像</li>
<li>数据库实时备份</li>
<li>多级索引 (卖家和买家各自分库索引)</li>
<li>search build</li>
<li>业务cache刷新</li>
<li>价格变化等重要业务消息</li>
</ol>
<h1>项目介绍</h1>
<p style="font-size: 14px;"> 名称:canal [kə'næl]</p>
<p style="font-size: 14px;"> 译意: 水道/管道/沟渠 </p>
<p style="font-size: 14px;"> 语言: 纯java开发</p>
<p style="font-size: 14px;"> 定位: 基于数据库增量日志解析,提供增量数据订阅&amp;消费,目前主要支持了mysql</p>
<p style="font-size: 14px;"> 关键词: mysql binlog parser / real-time / queue&topic </p>
<p style="font-size: 14px;"> </p>
<h2>工作原理</h2>
<h3 style="font-size: 14px;">mysql主备复制实现</h3>
<p><img src="http://dl.iteye.com/upload/attachment/0080/3086/468c1a14-e7ad-3290-9d3d-44ac501a7227.jpg" alt=""><br> 从上层来看,复制分成三步:
<ol>
<li>master将改变记录到二进制日志(binary log)中(这些记录叫做二进制日志事件,binary log events,可以通过show binlog events进行查看);</li>
<li>slave将master的binary log events拷贝到它的中继日志(relay log);</li>
<li>slave重做中继日志中的事件,将改变反映它自己的数据。</li>
</ol>
<h3>canal的工作原理:</h3>
<p><img width="590" src="http://dl.iteye.com/upload/attachment/0080/3107/c87b67ba-394c-3086-9577-9db05be04c95.jpg" alt="" height="273">
<p>原理相对比较简单:</p>
<ol>
<li>canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议</li>
<li>mysql master收到dump请求,开始推送binary log给slave(也就是canal)</li>
<li>canal解析binary log对象(原始为byte流)</li>
</ol>
## 背景
<h1>重要版本更新说明</h1>
早期,阿里巴巴 B2B 公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求 ,主要是基于trigger的方式获取增量变更。从 2010 年开始,公司开始逐步尝试数据库日志解析,获取增量变更进行同步,由此衍生出了增量订阅和消费业务,从此开启一段新纪元。
canal 1.1.x系列,参考release文档:<a href="https://github.com/alibaba/canal/releases">版本发布信息</a>
当前的 canal 支持源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x
1. 整体性能测试&优化,提升了150%. #726 参考: 【[Performance](https://github.com/alibaba/canal/wiki/Performance)】
2. 原生支持prometheus监控 #765 【[Prometheus QuickStart](https://github.com/alibaba/canal/wiki/Prometheus-QuickStart)】
3. 原生支持kafka消息投递 #695 【[Canal Kafka/RocketMQ QuickStart](https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart)】
4. 原生支持aliyun rds的binlog订阅 (解决自动主备切换/oss binlog离线解析) 参考: 【[Aliyun RDS QuickStart](https://github.com/alibaba/canal/wiki/aliyun-RDS-QuickStart)】
5. 原生支持docker镜像 #801 参考: 【[Docker QuickStart](https://github.com/alibaba/canal/wiki/Docker-QuickStart)】
基于日志增量订阅和消费的业务包括
- 数据库镜像
- 数据库实时备份
- 索引构建和实时维护(拆分异构索引、倒排索引等)
- 业务 cache 刷新
- 带业务逻辑的增量数据处理
<h1>相关文档</h1>
## 项目介绍
See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文档</a>
#### 名称:canal [kə'næl]
#### 译意: 水道/管道/沟渠
#### 产品定位: 基于数据库增量日志解析,提供增量数据订阅和消费
#### 关键词: MySQL binlog parser / real-time / queue&topic / index build
<h3><a name="table-of-contents" class="anchor" href="#table-of-contents"><span class="mini-icon mini-icon-link"></span></a>wiki文档列表</h3>
<ul>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/Home">Home</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/Introduction">Introduction</a></li>
<li>
<a class="internal present" href="https://github.com/alibaba/canal/wiki/QuickStart">QuickStart</a>
<ul>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/Docker-QuickStart">Docker QuickStart</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart">Canal Kafka/RocketMQ QuickStart</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/aliyun-RDS-QuickStart">Aliyun RDS QuickStart</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/Prometheus-QuickStart">Prometheus QuickStart</a></li>
</ul>
</li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/AdminGuide">AdminGuide</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/ClientExample">ClientExample</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/ClientAPI">ClientAPI</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/Performance">Performance</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/DevGuide">DevGuide</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/BinlogChange%28mysql5.6%29">BinlogChange(Mysql5.6)</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/BinlogChange%28MariaDB%29">BinlogChange(MariaDB)</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/TableMetaTSDB">TableMetaTSDB</a></li>
<li><a href="http://alibaba.github.com/canal/release.html">ReleaseNotes</a></li>
<li><a href="https://github.com/alibaba/canal/releases">Download</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/FAQ">FAQ</a></li>
</ul>
## 工作原理
<h1>多语言业务</h1>
#### MySQL主备复制原理
![](http://dl.iteye.com/upload/attachment/0080/3086/468c1a14-e7ad-3290-9d3d-44ac501a7227.jpg)
1. canal整体交互协议设计上使用了protobuf3.0,理论上可以支持绝大部分的多语言场景,欢迎大家提交多客户端的PR
* canal java客户端: <a href="https://github.com/alibaba/canal/wiki/ClientExample"> https://github.com/alibaba/canal/wiki/ClientExample </a>
* canal c#客户端开源项目地址:<a href="https://github.com/dotnetcore/CanalSharp"> https://github.com/dotnetcore/CanalSharp </a>
* canal go客户端开源项目地址:<a href="https://github.com/CanalClient/canal-go"> https://github.com/CanalClient/canal-go </a>
2. canal作为MySQL binlog的增量获取工具,可以将数据投递到MQ系统中,比如Kafka/RocketMQ,可以借助于MQ的多语言能力
* 参考文档: [Canal Kafka/RocketMQ QuickStart](https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart)
- MySQL master 将数据变更写入二进制日志( binary log, 其中记录叫做二进制日志事件binary log events,可以通过 show binlog events 进行查看)
- MySQL slave 将 master 的 binary log events 拷贝到它的中继日志(relay log)
- MySQL slave 重放 relay log 中事件,将数据变更反映它自己的数据
<h1>相关资料</h1>
#### canal 工作原理
![](http://dl.iteye.com/upload/attachment/0080/3107/c87b67ba-394c-3086-9577-9db05be04c95.jpg)
* ADC阿里技术嘉年华分享ppt (放在google docs上,可能需要翻墙): <a href="https://docs.google.com/presentation/d/1MkszUPYRDkfVPz9IqOT1LLT5d9tuwde_WC8GZvjaDRg/edit?usp=sharing">ppt下载</href>
* [与阿里巴巴的RocketMQ配合使用](https://github.com/apache/RocketMQ)
- canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议
- MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
- canal 解析 binary log 对象(原始为 byte 流)
<h1>相关开源</h1>
<ol>
<li>阿里巴巴分布式数据库同步系统(解决中美异地机房):<a href="http://github.com/alibaba/otter">http://github.com/alibaba/otter</a></li>
<li>阿里巴巴去Oracle数据迁移同步工具(目标支持MySQL/DRDS):<a href="http://github.com/alibaba/yugong">http://github.com/alibaba/yugong</a></li>
</ol>
## 重要版本更新说明
<h1>相关产品</h1>
<ol>
<li><a href="https://www.aliyun.com/product/drds?spm=5176.55326.cloudEssentials.71.69fd227dRPZj9K">阿里云分布式数据库DRDS</a></li>
<li><a href="https://www.aliyun.com/product/dts?spm=5176.7947010.cloudEssentials.80.33f734f4JOAxSP">阿里云数据传输服务DTS</a></li>
<li><a href="https://www.aliyun.com/product/dbs?spm=5176.54487.cloudEssentials.83.34b851a8GmVZg6">阿里云数据库备份服务DBS</a></li>
<li><a href="https://www.aliyun.com/product/dms?spm=5176.169464.cloudEssentials.81.2e1066feC1sBBL">阿里云数据管理服务DMS</a></li>
</ol>
canal 1.1.x 版本([release_note](https://github.com/alibaba/canal/releases)),性能与功能层面有较大的突破,重要提升包括:
<h1>问题反馈</h1>
<ol>
<li>qq交流群: 161559791 </li>
<li>邮件交流: jianghang115@gmail.com </li>
<li>新浪微博: agapple0002 </li>
<li>报告issue:<a href="https://github.com/alibaba/canal/issues">issues</a></li>
</ol>
- 整体性能测试&优化,提升了150%. #726 参考: [Performance](https://github.com/alibaba/canal/wiki/Performance)
- 原生支持prometheus监控 #765 [Prometheus QuickStart](https://github.com/alibaba/canal/wiki/Prometheus-QuickStart)
- 原生支持kafka消息投递 #695 [Canal Kafka/RocketMQ QuickStart](https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart)
- 原生支持aliyun rds的binlog订阅 (解决自动主备切换/oss binlog离线解析) 参考: [Aliyun RDS QuickStart](https://github.com/alibaba/canal/wiki/aliyun-RDS-QuickStart)
- 原生支持docker镜像 #801 参考: [Docker QuickStart](https://github.com/alibaba/canal/wiki/Docker-QuickStart)
<h3>最新更新</h3>
<ol>
<li>canal发布重大版本更新1.1.0,具体releaseNode参考:<a href="https://github.com/alibaba/canal/releases/tag/canal-1.1.0">https://github.com/alibaba/canal/releases/tag/canal-1.1.0</a></li>
<li>canal c#客户端开源项目地址:<a href="https://github.com/dotnetcore/CanalSharp"> https://github.com/dotnetcore/CanalSharp </a>,推荐! </li>
<li>canal QQ讨论群已经建立,群号:161559791 ,欢迎加入进行技术讨论。</li>
<li>canal消费端项目开源: Otter(分布式数据库同步系统),地址:<a href="https://github.com/alibaba/otter">https://github.com/alibaba/otter</a></li>
## 文档
<li>Canal已在阿里云推出商业化版本 <a href="https://www.aliyun.com/product/dts?spm=a2c4g.11186623.cloudEssentials.80.srdwr7">数据传输服务DTS</a>, 开通即用,免去部署维护的昂贵使用成本。DTS针对阿里云RDS、DRDS等产品进行了适配,解决了Binlog日志回收,主备切换、VPC网络切换等场景下的订阅高可用问题。同时,针对RDS进行了针对性的性能优化。出于稳定性、性能及成本的考虑,强烈推荐阿里云用户使用DTS产品。<a href="https://help.aliyun.com/document_detail/26592.html?spm=a2c4g.11174283.6.539.t1Y91E">DTS产品使用文档</a></li>
DTS支持阿里云RDS&DRDS的Binlog日志实时订阅,现推出首月免费体验,限时限量,<a href="https://common-buy.aliyun.com/?commodityCode=dtspre&request=%7b%22dts_function%22%3a%22data_subscribe%22%7d">立即体验>>></a>
</ol>
- [Home](https://github.com/alibaba/canal/wiki/Home)
- [Introduction](https://github.com/alibaba/canal/wiki/Introduction)
- [QuickStart](https://github.com/alibaba/canal/wiki/QuickStart)
- [Docker QuickStart](https://github.com/alibaba/canal/wiki/Docker-QuickStart)
- [Canal Kafka/RocketMQ QuickStart](https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart")
- [Aliyun RDS for MySQL QuickStart](https://github.com/alibaba/canal/wiki/aliyun-RDS-QuickStart)
- [Prometheus QuickStart](https://github.com/alibaba/canal/wiki/Prometheus-QuickStart)
- [AdminGuide](https://github.com/alibaba/canal/wiki/AdminGuide)
- [ClientExample](https://github.com/alibaba/canal/wiki/ClientExample)
- [ClientAPI](https://github.com/alibaba/canal/wiki/ClientAPI)
- [Performance](https://github.com/alibaba/canal/wiki/Performance)
- [DevGuide](https://github.com/alibaba/canal/wiki/DevGuide)
- [BinlogChange(MySQL 5.6)](https://github.com/alibaba/canal/wiki/BinlogChange%28mysql5.6%29)
- [BinlogChange(MariaDB)](https://github.com/alibaba/canal/wiki/BinlogChange%28MariaDB%29)
- [TableMetaTSDB](https://github.com/alibaba/canal/wiki/TableMetaTSDB)
- [ReleaseNotes](http://alibaba.github.com/canal/release.html)
- [Download](https://github.com/alibaba/canal/releases)
- [FAQ](https://github.com/alibaba/canal/wiki/FAQ)
## 多语言
canal 特别设计了 client-server 模式,交互协议使用 protobuf 3.0 , client 端可采用不同语言实现不同的消费逻辑,欢迎大家提交 pull request
- canal java 客户端: [https://github.com/alibaba/canal/wiki/ClientExample](https://github.com/alibaba/canal/wiki/ClientExample)
- canal c# 客户端: [https://github.com/dotnetcore/CanalSharp](https://github.com/dotnetcore/CanalSharp)
- canal go客户端: [https://github.com/CanalClient/canal-go](https://github.com/CanalClient/canal-go)
- canal php客户端: [https://github.com/xingwenge/canal-php](https://github.com/xingwenge/canal-php)
- canal Python客户端:[https://github.com/haozi3156666/canal-python](https://github.com/haozi3156666/canal-python)
canal 作为 MySQL binlog 增量获取和解析工具,可将变更记录投递到 MQ 系统中,比如 Kafka/RocketMQ,可以借助于 MQ 的多语言能力
- 参考文档: [Canal Kafka/RocketMQ QuickStart](https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart)
## 相关开源
- [canal 消费端开源项目: Otter](http://github.com/alibaba/otter)
- [阿里巴巴去 Oracle 数据迁移同步工具: yugong](http://github.com/alibaba/yugong)
## 问题反馈
- 邮件: jianghang115@gmail.com
- 报告 issue: [github issues](https://github.com/alibaba/canal/issues)
## 其他资料
- [ ADC 阿里技术嘉年华分享 ppt ( google docs)](https://docs.google.com/presentation/d/1MkszUPYRDkfVPz9IqOT1LLT5d9tuwde_WC8GZvjaDRg/edit?usp=sharing)

View File

@@ -0,0 +1,225 @@
<?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">
<parent>
<artifactId>canal-admin</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.4</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>canal-admin-server</artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.protocol</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.41.1</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>canal-admin-${project.version}</finalName>
<plugins>
<plugin>
<groupId>io.repaint.maven</groupId>
<artifactId>tiles-maven-plugin</artifactId>
<version>2.12</version>
<extensions>true</extensions>
<configuration>
<tiles>
<tile>io.ebean.tile:enhancement:11.41.1</tile>
</tiles>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>assemble</id>
<goals>
<goal>single</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<attach>false</attach>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.alibaba.otter.canal.admin.CanalAdminApplication</mainClass>
</manifest>
</archive>
<excludes>
<exclude>**/*.properties</exclude>
<exclude>**/*.xml</exclude>
<exclude>**/*.yml</exclude>
<exclude>static/**</exclude>
<exclude>public/**</exclude>
<exclude>templates/**</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>npm</id>
<activation>
<property>
<name>env</name>
<value>npm</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<filesets>
<fileset>
<directory>src/main/resources/public</directory>
</fileset>
</filesets>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
<executions>
<execution>
<id>copy Vue.js frontend content</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>src/main/resources/public</outputDirectory>
<overwrite>true</overwrite>
<resources>
<resource>
<directory>${project.parent.basedir}/canal-admin-ui/target/dist</directory>
<includes>
<include>static/</include>
<include>index.html</include>
<include>avatar.gif</include>
<include>logo.png</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
<property>
<name>env</name>
<value>!release</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>${basedir}/src/main/assembly/dev.xml</descriptor>
</descriptors>
<finalName>canal-admin</finalName>
<outputDirectory>${project.build.directory}</outputDirectory>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>release</id>
<activation>
<property>
<name>env</name>
<value>release</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>${basedir}/src/main/assembly/release.xml</descriptor>
</descriptors>
<finalName>canal.admin-${project.version}</finalName>
<outputDirectory>${project.basedir}/../../target</outputDirectory>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,52 @@
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
<id>dist</id>
<formats>
<format>dir</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>.</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>README*</include>
</includes>
</fileSet>
<fileSet>
<directory>./src/main/bin</directory>
<outputDirectory>bin</outputDirectory>
<includes>
<include>**/*</include>
</includes>
<fileMode>0755</fileMode>
</fileSet>
<fileSet>
<directory>./src/main/resources</directory>
<outputDirectory>/conf</outputDirectory>
<includes>
<include>application.yml</include>
<include>logback.xml</include>
<include>public/**</include>
<include>canal_manager.sql</include>
<include>canal-template.properties</include>
<include>instance-template.properties</include>
</includes>
</fileSet>
<fileSet>
<directory>target</directory>
<outputDirectory>logs</outputDirectory>
<excludes>
<exclude>**/*</exclude>
</excludes>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
<excludes>
<exclude>junit:junit</exclude>
</excludes>
</dependencySet>
</dependencySets>
</assembly>

View File

@@ -0,0 +1,53 @@
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
<id>dist</id>
<formats>
<format>tar.gz</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>.</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>README*</include>
</includes>
</fileSet>
<fileSet>
<directory>./src/main/bin</directory>
<outputDirectory>bin</outputDirectory>
<includes>
<include>**/*</include>
</includes>
<fileMode>0755</fileMode>
</fileSet>
<fileSet>
<directory>./src/main/resources</directory>
<outputDirectory>/conf</outputDirectory>
<includes>
<include>application.yml</include>
<include>logback.xml</include>
<include>public/**</include>
<include>canal_manager.sql</include>
<include>canal-template.properties</include>
<include>instance-template.properties</include>
</includes>
</fileSet>
<fileSet>
<directory>target</directory>
<outputDirectory>logs</outputDirectory>
<excludes>
<exclude>**/*</exclude>
</excludes>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
<excludes>
<exclude>junit:junit</exclude>
</excludes>
</dependencySet>
</dependencySets>
</assembly>

View File

@@ -0,0 +1,5 @@
#!/bin/bash
sh stop.sh
sh startup.sh

View File

@@ -0,0 +1,22 @@
@echo off
@if not "%ECHO%" == "" echo %ECHO%
@if "%OS%" == "Windows_NT" setlocal
set ENV_PATH=.\
if "%OS%" == "Windows_NT" set ENV_PATH=%~dp0%
set conf_dir=%ENV_PATH%\..\conf
set CLASSPATH=%conf_dir%
set CLASSPATH=%conf_dir%\..\lib\*;%CLASSPATH%
set JAVA_MEM_OPTS= -Xms128m -Xmx512m
set JAVA_OPTS_EXT= -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dapplication.codeset=UTF-8 -Dfile.encoding=UTF-8
set ADAPTER_OPTS= -DappName=canal-admin
set JAVA_OPTS= %JAVA_MEM_OPTS% %JAVA_OPTS_EXT% %ADAPTER_OPTS%
set CMD_STR= java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.admin.CanalAdminApplication
echo start cmd : %CMD_STR%
java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.admin.CanalAdminApplication

View File

@@ -0,0 +1,84 @@
#!/bin/bash
current_path=`pwd`
case "`uname`" in
Linux)
bin_abs_path=$(readlink -f $(dirname $0))
;;
*)
bin_abs_path=`cd $(dirname $0); pwd`
;;
esac
base=${bin_abs_path}/..
export LANG=en_US.UTF-8
export BASE=$base
if [ -f $base/bin/adapter.pid ] ; then
echo "found adapter.pid , Please run stop.sh first ,then startup.sh" 2>&2
exit 1
fi
if [ ! -d $base/logs ] ; then
mkdir -p $base/logs
fi
## set java path
if [ -z "$JAVA" ] ; then
JAVA=$(which java)
fi
ALIBABA_JAVA="/usr/alibaba/java/bin/java"
TAOBAO_JAVA="/opt/taobao/java/bin/java"
if [ -z "$JAVA" ]; then
if [ -f $ALIBABA_JAVA ] ; then
JAVA=$ALIBABA_JAVA
elif [ -f $TAOBAO_JAVA ] ; then
JAVA=$TAOBAO_JAVA
else
echo "Cannot find a Java JDK. Please set either set JAVA or put java (>=1.5) in your PATH." 2>&2
exit 1
fi
fi
case "$#"
in
0 )
;;
2 )
if [ "$1" = "debug" ]; then
DEBUG_PORT=$2
DEBUG_SUSPEND="n"
JAVA_DEBUG_OPT="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=$DEBUG_PORT,server=y,suspend=$DEBUG_SUSPEND"
fi
;;
* )
echo "THE PARAMETERS MUST BE TWO OR LESS.PLEASE CHECK AGAIN."
exit;;
esac
str=`file -L $JAVA | grep 64-bit`
if [ -n "$str" ]; then
JAVA_OPTS="-server -Xms2048m -Xmx3072m"
else
JAVA_OPTS="-server -Xms1024m -Xmx1024m"
fi
JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=250 -XX:+UseGCOverheadLimit -XX:+ExplicitGCInvokesConcurrent -XX:+PrintAdaptiveSizePolicy -XX:+PrintTenuringDistribution"
JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8"
CANAL_OPTS="-DappName=canal-admin"
for i in $base/lib/*;
do CLASSPATH=$i:"$CLASSPATH";
done
CLASSPATH="$base/conf:$CLASSPATH";
echo "cd to $bin_abs_path for workaround relative path"
cd $bin_abs_path
echo CLASSPATH :$CLASSPATH
$JAVA $JAVA_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.admin.CanalAdminApplication 1>>/dev/null 2>&1 &
echo $! > $base/bin/admin.pid
echo "cd to $current_path for continue"
cd $current_path

View File

@@ -0,0 +1,65 @@
#!/bin/bash
cygwin=false;
linux=false;
case "`uname`" in
CYGWIN*)
cygwin=true
;;
Linux*)
linux=true
;;
esac
get_pid() {
STR=$1
PID=$2
if $cygwin; then
JAVA_CMD="$JAVA_HOME\bin\java"
JAVA_CMD=`cygpath --path --unix $JAVA_CMD`
JAVA_PID=`ps |grep $JAVA_CMD |awk '{print $1}'`
else
if $linux; then
if [ ! -z "$PID" ]; then
JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'`
else
JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep -v grep|awk '{print $2}'`
fi
else
if [ ! -z "$PID" ]; then
JAVA_PID=`ps aux |grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'`
else
JAVA_PID=`ps aux |grep "$STR"|grep -v grep|awk '{print $2}'`
fi
fi
fi
echo $JAVA_PID;
}
base=`dirname $0`/..
pidfile=$base/bin/admin.pid
if [ ! -f "$pidfile" ];then
echo "canal-admin is not running. exists"
exit
fi
pid=`cat $pidfile`
if [ "$pid" == "" ] ; then
pid=`get_pid "appName=canal-admin"`
fi
echo -e "`hostname`: stopping canal $pid ... "
kill $pid
LOOPS=0
while (true);
do
gpid=`get_pid "appName=canal-admin" "$pid"`
if [ "$gpid" == "" ] ; then
echo "Oook! cost:$LOOPS"
`rm $pidfile`
break;
fi
let LOOPS=LOOPS+1
sleep 1
done

View File

@@ -0,0 +1,21 @@
package com.alibaba.otter.canal.admin;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 启动入口
*
* @author rewerma @ 2018-10-20
* @version 1.0.0
*/
@SpringBootApplication
public class CanalAdminApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(CanalAdminApplication.class);
application.setBannerMode(Banner.Mode.OFF);
application.run(args);
}
}

View File

@@ -0,0 +1,15 @@
package com.alibaba.otter.canal.admin.common;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
public class DaemonThreadFactory implements ThreadFactory {
public static final ThreadFactory daemonThreadFactory = new DaemonThreadFactory();
public Thread newThread(Runnable r) {
Thread t = Executors.defaultThreadFactory().newThread(r);
t.setDaemon(true);
return t;
}
}

View File

@@ -0,0 +1,50 @@
package com.alibaba.otter.canal.admin.common;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TemplateConfigLoader {
private static final Logger logger = LoggerFactory.getLogger(TemplateConfigLoader.class);
public static final String CONF_DIR = "conf";
public static final String CANAL_CONFIG_TMP = "canal-template.properties";
public static final String INSTANCE_CONFIG_TMP = "instance-template.properties";
public static String loadCanalConfig() {
return loadFile(CANAL_CONFIG_TMP);
}
public static String loadInstanceConfig() {
return loadFile(INSTANCE_CONFIG_TMP);
}
private static String loadFile(String fileName) {
File configFile = new File(".." + File.separator + CONF_DIR + File.separator + fileName);
if (!configFile.exists()) {
URL url = TemplateConfigLoader.class.getClassLoader().getResource("");
if (url != null) {
configFile = new File(url.getPath() + fileName + File.separator);
}
}
if (!configFile.exists()) {
return null;
}
try (InputStream in = new FileInputStream(configFile)) {
byte[] bytes = new byte[in.available()];
in.read(bytes);
return new String(bytes, StandardCharsets.UTF_8);
} catch (IOException e) {
logger.error("Read " + fileName + " error", e);
}
return null;
}
}

View File

@@ -0,0 +1,17 @@
package com.alibaba.otter.canal.admin.common;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Threads {
public static int pool = 60;
public static final ExecutorService executorService = new ThreadPoolExecutor(pool,
pool,
0L,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(pool * 20),
DaemonThreadFactory.daemonThreadFactory);
}

View File

@@ -0,0 +1,49 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.otter.canal.admin.common.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* Service Logic Exception
*/
@ResponseStatus(value = HttpStatus.SERVICE_UNAVAILABLE)
public class ServiceException extends RuntimeException {
private static final long serialVersionUID = -663217666968123330L;
public ServiceException(){
}
public ServiceException(String message){
super(message);
}
public ServiceException(String message, Throwable cause){
super(message, cause);
}
public ServiceException(Throwable cause){
super(cause);
}
public ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace){
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -0,0 +1,40 @@
package com.alibaba.otter.canal.admin.config;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.ebean.EbeanServer;
import io.ebean.EbeanServerFactory;
import io.ebean.config.ServerConfig;
import io.ebean.config.UnderscoreNamingConvention;
/**
* Ebean 配置
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Configuration
public class EbeanConfig {
@Bean("ebeanServer")
public EbeanServer ebeanServer(DataSource dataSource) {
ServerConfig serverConfig = new ServerConfig();
serverConfig.setDefaultServer(true);
serverConfig.setNamingConvention(new UnderscoreNamingConvention());
List<String> packages = new ArrayList<>();
packages.add("com.alibaba.otter.canal.admin.model");
serverConfig.setPackages(packages);
serverConfig.setName("ebeanServer");
serverConfig.setDataSource(dataSource);
serverConfig.setDatabaseSequenceBatchSize(1);
serverConfig.setDdlGenerate(false);
serverConfig.setDdlRun(false);
return EbeanServerFactory.create(serverConfig);
}
}

View File

@@ -0,0 +1,34 @@
package com.alibaba.otter.canal.admin.config;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* spring util配置类
*
* @author rewerma @ 2018-10-20
* @version 1.0.0
*/
@Component
public class SpringContext implements ApplicationContextAware {
private static ApplicationContext context;
/*
* 注入ApplicationContext
*/
public void setApplicationContext(final ApplicationContext context) throws BeansException {
// 在加载Spring时自动获得context
SpringContext.context = context;
}
public static Object getBean(final String beanName) {
return SpringContext.context.getBean(beanName);
}
public static Object getBean(final Class<?> clz) {
return context.getBean(clz);
}
}

View File

@@ -0,0 +1,92 @@
package com.alibaba.otter.canal.admin.config;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.alibaba.otter.canal.admin.controller.UserController;
import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.User;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* 相关MVC拦截器配置
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
Object o) throws Exception {
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "*");
httpServletResponse.setHeader("Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization, X-Token");
httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpServletResponse.setHeader("Access-Control-Max-Age", String.valueOf(3600 * 24));
if (HttpMethod.OPTIONS.toString().equals(httpServletRequest.getMethod())) {
httpServletResponse.setStatus(HttpStatus.NO_CONTENT.value());
return false;
}
return true;
}
}).addPathPatterns("/api/**");
registry.addInterceptor(new HandlerInterceptor() {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
Object o) throws Exception {
String token = httpServletRequest.getHeader("X-Token");
boolean valid = false;
if (token != null) {
User user = UserController.loginUsers.getIfPresent(token);
if (user != null) {
valid = true;
httpServletRequest.setAttribute("user", user);
httpServletRequest.setAttribute("token", token);
}
}
if (!valid) {
BaseModel baseModel = BaseModel.getInstance(null);
baseModel.setCode(50014);
baseModel.setMessage("Expired token");
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(baseModel);
try {
httpServletResponse.setContentType("application/json;charset=UTF-8");
PrintWriter out = httpServletResponse.getWriter();
out.print(json);
} catch (Throwable e) {
throw new RuntimeException(e);
}
return false;
}
return true;
}
})
.addPathPatterns("/api/**")
.excludePathPatterns("/api/**/config/**")
.excludePathPatterns("/api/**/user/login")
.excludePathPatterns("/api/**/user/logout")
.excludePathPatterns("/api/**/user/info");
}
}

View File

@@ -0,0 +1,131 @@
package com.alibaba.otter.canal.admin.connector;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
/**
* canal admin操作客户端
*
* @author agapple 2019年8月31日 下午12:46:29
* @since 1.1.4
*/
public interface AdminConnector {
/**
* 链接对应的canal server
*
* @throws CanalClientException
*/
void connect() throws ServiceException;
/**
* 释放链接
*
* @throws CanalClientException
*/
void disconnect() throws ServiceException;
/**
* 获取Canal Server状态
*
* @return 状态代码
*/
boolean check();
/**
* 启动Canal Server
*
* @return 是否成功
*/
boolean start();
/**
* 停止Canal Server
*
* @return 是否成功
*/
boolean stop();
/**
* 重启Canal Server
*
* @return 是否成功
*/
boolean restart();
/**
* 获取所有当前节点下运行中的实例
*
* @return 实例信息
*/
String getRunningInstances();
/**
* 通过实例名检查
*
* @param destination
* @return
*/
boolean checkInstance(String destination);
/**
* 通过实例名启动实例
*
* @param destination 实例名
* @return 是否成功
*/
boolean startInstance(String destination);
/**
* 通过实例名关闭实例
*
* @param destination 实例名
* @return 是否成功
*/
boolean stopInstance(String destination);
/**
* 通过实例名释放,主要针对cluster模式有效(通知当前主机释放instance运行交给其他人来抢占)
*
* @param destination 实例名
* @return 是否成功
*/
boolean releaseInstance(String destination);
/**
* 通过实例名重启实例
*
* @param destination 实例名
* @return 是否成功
*/
boolean restartInstance(String destination);
/**
* 获取Canal Server日志列表
*
* @return 日志信息
*/
String listCanalLog();
/**
* 获取Canal Server日志
*
* @return 日志信息
*/
String canalLog(int lines);
/**
* 获取Instance的机器日志列表
*
* @param destination
*/
String listInstanceLog(String destination);
/**
* 通过实例名获取实例日志
*
* @return 日志信息
*/
String instanceLog(String destination, String fileName, int lines);
}

View File

@@ -0,0 +1,323 @@
package com.alibaba.otter.canal.admin.connector;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.Channel;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.lang.BooleanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.protocol.AdminPacket.Ack;
import com.alibaba.otter.canal.protocol.AdminPacket.ClientAuth;
import com.alibaba.otter.canal.protocol.AdminPacket.Handshake;
import com.alibaba.otter.canal.protocol.AdminPacket.InstanceAdmin;
import com.alibaba.otter.canal.protocol.AdminPacket.LogAdmin;
import com.alibaba.otter.canal.protocol.AdminPacket.Packet;
import com.alibaba.otter.canal.protocol.AdminPacket.PacketType;
import com.alibaba.otter.canal.protocol.AdminPacket.ServerAdmin;
import com.alibaba.otter.canal.protocol.SecurityUtil;
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
import com.google.protobuf.ByteString;
/**
* 基于netty实现的admin控制
*
* @author agapple 2019年8月26日 上午10:23:44
* @since 1.1.4
*/
public class SimpleAdminConnector implements AdminConnector {
private static final Logger logger = LoggerFactory.getLogger(SimpleAdminConnector.class);
private String user;
private String passwd;
private SocketAddress address;
private int soTimeout = 60000; // milliseconds
private int idleTimeout = 60 * 60 * 1000; // client和server之间的空闲链接超时的时间,默认为1小时
private final ByteBuffer readHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
private final ByteBuffer writeHeader = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
private SocketChannel channel;
private ReadableByteChannel readableChannel;
private WritableByteChannel writableChannel;
private volatile boolean connected = false;
public SimpleAdminConnector(String ip, int port, String user, String passwd){
this.address = new InetSocketAddress(ip, port);
this.user = user;
this.passwd = passwd;
}
@Override
public void connect() throws ServiceException {
try {
if (connected) {
return;
}
channel = SocketChannel.open();
channel.socket().setSoTimeout(soTimeout);
channel.connect(address);
readableChannel = Channels.newChannel(channel.socket().getInputStream());
writableChannel = Channels.newChannel(channel.socket().getOutputStream());
Packet p = Packet.parseFrom(readNextPacket());
if (p.getVersion() != 1) {
throw new CanalClientException("unsupported version at this client.");
}
if (p.getType() != PacketType.HANDSHAKE) {
throw new CanalClientException("expect handshake but found other type.");
}
Handshake handshake = Handshake.parseFrom(p.getBody());
ByteString seed = handshake.getSeeds(); // seed for auth
String newPasswd = passwd;
if (passwd != null) {
// encode passwd
newPasswd = SecurityUtil.byte2HexStr(SecurityUtil.scramble411(passwd.getBytes(), seed.toByteArray()));
}
ClientAuth ca = ClientAuth.newBuilder()
.setUsername(user != null ? user : "")
.setPassword(ByteString.copyFromUtf8(newPasswd != null ? newPasswd : ""))
.setNetReadTimeout(idleTimeout)
.setNetWriteTimeout(idleTimeout)
.build();
writeWithHeader(Packet.newBuilder()
.setType(PacketType.CLIENTAUTHENTICATION)
.setBody(ca.toByteString())
.build()
.toByteArray());
//
Packet ack = Packet.parseFrom(readNextPacket());
if (ack.getType() != PacketType.ACK) {
throw new CanalClientException("unexpected packet type when ack is expected");
}
Ack ackBody = Ack.parseFrom(ack.getBody());
if (ackBody.getCode() > 0) {
throw new ServiceException("something goes wrong when doing authentication: " + ackBody.getMessage());
}
connected = true;
} catch (IOException e) {
throw new ServiceException(e);
} catch (NoSuchAlgorithmException e) {
throw new ServiceException(e);
}
}
@Override
public void disconnect() throws ServiceException {
if (!connected) {
return;
}
connected = false;
if (readableChannel != null) {
quietlyClose(readableChannel);
readableChannel = null;
}
if (writableChannel != null) {
quietlyClose(writableChannel);
writableChannel = null;
}
if (channel != null) {
quietlyClose(channel);
channel = null;
}
}
@Override
public boolean check() {
return BooleanUtils.toBoolean(Integer.parseInt(doServerAdmin("check")));
}
@Override
public boolean start() {
return BooleanUtils.toBoolean(Integer.parseInt(doServerAdmin("start")));
}
@Override
public boolean stop() {
return BooleanUtils.toBoolean(Integer.parseInt(doServerAdmin("stop")));
}
@Override
public boolean restart() {
return BooleanUtils.toBoolean(Integer.parseInt(doServerAdmin("restart")));
}
@Override
public String getRunningInstances() {
return doServerAdmin("list");
}
@Override
public boolean checkInstance(String destination) {
return BooleanUtils.toBoolean(Integer.parseInt(doInstanceAdmin(destination, "check")));
}
@Override
public boolean startInstance(String destination) {
return BooleanUtils.toBoolean(Integer.parseInt(doInstanceAdmin(destination, "start")));
}
@Override
public boolean stopInstance(String destination) {
return BooleanUtils.toBoolean(Integer.parseInt(doInstanceAdmin(destination, "stop")));
}
@Override
public boolean releaseInstance(String destination) {
return BooleanUtils.toBoolean(Integer.parseInt(doInstanceAdmin(destination, "release")));
}
@Override
public boolean restartInstance(String destination) {
return BooleanUtils.toBoolean(Integer.parseInt(doInstanceAdmin(destination, "restart")));
}
@Override
public String listCanalLog() {
return doLogAdmin("server", "list", null, null, 0);
}
@Override
public String canalLog(int lines) {
return doLogAdmin("server", "file", null, null, lines);
}
@Override
public String listInstanceLog(String destination) {
return doLogAdmin("instance", "list", destination, null, 0);
}
@Override
public String instanceLog(String destination, String fileName, int lines) {
return doLogAdmin("instance", "file", destination, fileName, lines);
}
// ==================== helper method ====================
private String doServerAdmin(String action) {
try {
writeWithHeader(Packet.newBuilder()
.setType(PacketType.SERVER)
.setBody(ServerAdmin.newBuilder().setAction(action).build().toByteString())
.build()
.toByteArray());
Packet p = Packet.parseFrom(readNextPacket());
Ack ack = Ack.parseFrom(p.getBody());
if (ack.getCode() > 0) {
throw new ServiceException("failed to subscribe with reason: " + ack.getMessage());
}
return ack.getMessage();
} catch (IOException e) {
throw new ServiceException(e);
}
}
private String doInstanceAdmin(String destination, String action) {
try {
writeWithHeader(Packet.newBuilder()
.setType(PacketType.INSTANCE)
.setBody(InstanceAdmin.newBuilder()
.setDestination(destination)
.setAction(action)
.build()
.toByteString())
.build()
.toByteArray());
Packet p = Packet.parseFrom(readNextPacket());
Ack ack = Ack.parseFrom(p.getBody());
if (ack.getCode() > 0) {
throw new ServiceException("failed to subscribe with reason: " + ack.getMessage());
}
return ack.getMessage();
} catch (IOException e) {
throw new ServiceException(e);
}
}
private String doLogAdmin(String type, String action, String destination, String file, int count) {
try {
writeWithHeader(Packet.newBuilder()
.setType(PacketType.LOG)
.setBody(LogAdmin.newBuilder()
.setType(type)
.setAction(action)
.setDestination(destination == null ? "" : destination)
.setFile(file == null ? "" : file)
.setCount(count)
.build()
.toByteString())
.build()
.toByteArray());
Packet p = Packet.parseFrom(readNextPacket());
Ack ack = Ack.parseFrom(p.getBody());
if (ack.getCode() > 0) {
throw new ServiceException("failed to subscribe with reason: " + ack.getMessage());
}
return ack.getMessage();
} catch (IOException e) {
throw new ServiceException(e);
}
}
private void writeWithHeader(byte[] body) throws IOException {
writeWithHeader(writableChannel, body);
}
private byte[] readNextPacket() throws IOException {
return readNextPacket(readableChannel);
}
private void writeWithHeader(WritableByteChannel channel, byte[] body) throws IOException {
writeHeader.clear();
writeHeader.putInt(body.length);
writeHeader.flip();
channel.write(writeHeader);
channel.write(ByteBuffer.wrap(body));
}
private byte[] readNextPacket(ReadableByteChannel channel) throws IOException {
readHeader.clear();
read(channel, readHeader);
int bodyLen = readHeader.getInt(0);
ByteBuffer bodyBuf = ByteBuffer.allocate(bodyLen).order(ByteOrder.BIG_ENDIAN);
read(channel, bodyBuf);
return bodyBuf.array();
}
private void read(ReadableByteChannel channel, ByteBuffer buffer) throws IOException {
while (buffer.hasRemaining()) {
int r = channel.read(buffer);
if (r == -1) {
throw new IOException("end of stream when reading header");
}
}
}
private void quietlyClose(Channel channel) {
try {
channel.close();
} catch (IOException e) {
logger.warn("exception on closing channel:{} \n {}", channel, e);
}
}
}

View File

@@ -0,0 +1,35 @@
package com.alibaba.otter.canal.admin.connector;
import java.util.function.Function;
import com.alibaba.otter.canal.admin.config.SpringContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
public class SimpleAdminConnectors {
private static final Logger logger = LoggerFactory.getLogger(SimpleAdminConnectors.class);
public static <R> R execute(String ip, int port, Function<AdminConnector, R> function) {
Environment env = (Environment) SpringContext.getBean(Environment.class);
String defaultUser = env.getProperty("canal.adminUser", "admin");
String defaultPasswd = env.getProperty("canal.adminPasswd", "admin");
return execute(ip, port, defaultUser, defaultPasswd, function);
}
public static <R> R execute(String ip, int port, String user, String passwd, Function<AdminConnector, R> function) {
SimpleAdminConnector connector = new SimpleAdminConnector(ip, port, user, passwd);
try {
connector.connect();
return function.apply(connector);
} catch (Exception e) {
logger.error(e.getMessage());
} finally {
connector.disconnect();
}
return null;
}
}

View File

@@ -0,0 +1,89 @@
package com.alibaba.otter.canal.admin.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.CanalCluster;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.admin.service.CanalClusterService;
import com.alibaba.otter.canal.admin.service.NodeServerService;
@RestController
@RequestMapping("/api/{env}/canal")
public class CanalClusterController {
@Autowired
CanalClusterService canalClusterServic;
@Autowired
NodeServerService nodeServerService;
@GetMapping(value = "/clusters")
public BaseModel<List<CanalCluster>> clusters(CanalCluster canalCluster, @PathVariable String env) {
return BaseModel.getInstance(canalClusterServic.findList(canalCluster));
}
@PostMapping(value = "/cluster")
public BaseModel<String> save(@RequestBody CanalCluster canalCluster, @PathVariable String env) {
canalClusterServic.save(canalCluster);
return BaseModel.getInstance("success");
}
@GetMapping(value = "/cluster/{id}")
public BaseModel<CanalCluster> detail(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(canalClusterServic.detail(id));
}
@PutMapping(value = "/cluster")
public BaseModel<String> update(@RequestBody CanalCluster canalCluster, @PathVariable String env) {
canalClusterServic.update(canalCluster);
return BaseModel.getInstance("success");
}
@DeleteMapping(value = "/cluster/{id}")
public BaseModel<String> delete(@PathVariable Long id, @PathVariable String env) {
canalClusterServic.delete(id);
return BaseModel.getInstance("success");
}
@GetMapping(value = "/clustersAndServers")
public BaseModel<List<?>> clustersAndServers(@PathVariable String env) {
List<CanalCluster> clusters = canalClusterServic.findList(new CanalCluster());
JSONObject group = new JSONObject();
group.put("label", "集群");
JSONArray jsonArray = new JSONArray();
clusters.forEach(cluster -> {
JSONObject item = new JSONObject();
item.put("label", cluster.getName());
item.put("value", "cluster:" + cluster.getId());
jsonArray.add(item);
});
group.put("options", jsonArray);
NodeServer param = new NodeServer();
param.setClusterId(-1L);
List<NodeServer> servers = nodeServerService.findAll(param); // 取所有standalone的节点
JSONObject group2 = new JSONObject();
group2.put("label", "单机主机");
JSONArray jsonArray2 = new JSONArray();
servers.forEach(server -> {
JSONObject item = new JSONObject();
item.put("label", server.getName());
item.put("value", "server:" + server.getId());
jsonArray2.add(item);
});
group2.put("options", jsonArray2);
List<JSONObject> result = new ArrayList<>();
result.add(group);
result.add(group2);
return BaseModel.getInstance(result);
}
}

View File

@@ -0,0 +1,58 @@
package com.alibaba.otter.canal.admin.controller;
import com.alibaba.otter.canal.admin.common.TemplateConfigLoader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.CanalConfig;
import com.alibaba.otter.canal.admin.service.CanalConfigService;
/**
* Canal主配置管理控制层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@RestController
@RequestMapping("/api/{env}/canal")
public class CanalConfigController {
@Autowired
CanalConfigService canalConfigService;
/**
* 获取配置信息
*
* @param env 环境变量
* @return 配置信息
*/
@GetMapping(value = "/config/{clusterId}/{serverId}")
public BaseModel<CanalConfig> canalConfig(@PathVariable Long clusterId, @PathVariable Long serverId,
@PathVariable String env) {
return BaseModel.getInstance(canalConfigService.getCanalConfig(clusterId, serverId));
}
/**
* 修改配置
*
* @param canalConfig 配置信息对象
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/config")
public BaseModel<String> updateConfig(@RequestBody CanalConfig canalConfig, @PathVariable String env) {
canalConfigService.updateContent(canalConfig);
return BaseModel.getInstance("success");
}
@GetMapping(value = "/config/template")
public BaseModel<String> template(@PathVariable String env) {
return BaseModel.getInstance(TemplateConfigLoader.loadCanalConfig());
}
}

View File

@@ -0,0 +1,181 @@
package com.alibaba.otter.canal.admin.controller;
import java.util.List;
import java.util.Map;
import com.alibaba.otter.canal.admin.common.TemplateConfigLoader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
import com.alibaba.otter.canal.admin.model.Pager;
import com.alibaba.otter.canal.admin.service.CanalInstanceService;
/**
* Canal Instance配置管理控制层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@RestController
@RequestMapping("/api/{env}/canal")
public class CanalInstanceController {
@Autowired
CanalInstanceService canalInstanceConfigService;
/**
* 实例配置列表
*
* @param canalInstanceConfig 查询对象
* @param env 环境变量
* @return 实例列表
*/
@GetMapping(value = "/instances")
public BaseModel<Pager<CanalInstanceConfig>> list(CanalInstanceConfig canalInstanceConfig,
Pager<CanalInstanceConfig> pager, @PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.findList(canalInstanceConfig, pager));
}
/**
* 保存实例配置
*
* @param canalInstanceConfig 实例配置对象
* @param env 环境变量
* @return 是否成功
*/
@PostMapping(value = "/instance")
public BaseModel<String> save(@RequestBody CanalInstanceConfig canalInstanceConfig, @PathVariable String env) {
canalInstanceConfigService.save(canalInstanceConfig);
return BaseModel.getInstance("success");
}
/**
* 实例详情信息
*
* @param id 实例配置id
* @param env 环境变量
* @return 实例信息
*/
@GetMapping(value = "/instance")
public BaseModel<CanalInstanceConfig> config(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.detail(id));
}
/**
* 实例详情信息
*
* @param id 实例配置id
* @param env 环境变量
* @return 实例信息
*/
@GetMapping(value = "/instance/{id}")
public BaseModel<CanalInstanceConfig> detail(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.detail(id));
}
/**
* 修改实例配置
*
* @param canalInstanceConfig 实例配置信息
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/instance")
public BaseModel<String> update(@RequestBody CanalInstanceConfig canalInstanceConfig, @PathVariable String env) {
canalInstanceConfigService.updateContent(canalInstanceConfig);
return BaseModel.getInstance("success");
}
/**
* 删除实例配置
*
* @param id 实例配置id
* @param env 环境变量
* @return 是否成功
*/
@DeleteMapping(value = "/instance/{id}")
public BaseModel<String> delete(@PathVariable Long id, @PathVariable String env) {
canalInstanceConfigService.delete(id);
return BaseModel.getInstance("success");
}
/**
* 启动远程实例
*
* @param id 实例配置id
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/instance/start/{id}/{nodeId}")
public BaseModel<Boolean> start(@PathVariable Long id, @PathVariable Long nodeId, @PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.remoteOperation(id, nodeId, "start"));
}
/**
* 关闭远程实例
*
* @param id 实例配置id
* @param nodeId 节点id
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/instance/stop/{id}/{nodeId}")
public BaseModel<Boolean> stop(@PathVariable Long id, @PathVariable Long nodeId, @PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.remoteOperation(id, nodeId, "stop"));
}
/**
* 通过操作instance状态启动/停止远程instance
*
* @param id 实例配置id
* @param option 操作类型: start/stop
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/instance/status/{id}")
public BaseModel<Boolean> instanceStart(@PathVariable Long id, @RequestParam String option,
@PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.instanceOperation(id, option));
}
/**
* 获取远程实例运行日志
*
* @param id 实例配置id
* @param nodeId 节点id
* @param env 环境变量
* @return 实例日志信息
*/
@GetMapping(value = "/instance/log/{id}/{nodeId}")
public BaseModel<Map<String, String>> instanceLog(@PathVariable Long id, @PathVariable Long nodeId,
@PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.remoteInstanceLog(id, nodeId));
}
/**
* 通过Server id获取所有活动的Instance
*
* @param serverId 节点id
* @param env 环境变量
* @return 实例列表
*/
@GetMapping(value = "/active/instances/{serverId}")
public BaseModel<List<CanalInstanceConfig>> activeInstances(@PathVariable Long serverId, @PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.findActiveInstanceByServerId(serverId));
}
@GetMapping(value = "/instance/template")
public BaseModel<String> template(@PathVariable String env) {
return BaseModel.getInstance(TemplateConfigLoader.loadInstanceConfig());
}
}

View File

@@ -0,0 +1,144 @@
package com.alibaba.otter.canal.admin.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.admin.model.Pager;
import com.alibaba.otter.canal.admin.service.NodeServerService;
/**
* 节点信息控制层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@RestController
@RequestMapping("/api/{env}")
public class NodeServerController {
@Autowired
NodeServerService nodeServerService;
/**
* 获取所有节点信息列表
*
* @param nodeServer 筛选条件
* @param env 环境变量
* @return 节点信息列表
*/
@GetMapping(value = "/nodeServers")
public BaseModel<Pager<NodeServer>> nodeServers(NodeServer nodeServer, Pager<NodeServer> pager,
@PathVariable String env) {
return BaseModel.getInstance(nodeServerService.findList(nodeServer, pager));
}
/**
* 保存节点信息
*
* @param nodeServer 节点信息
* @param env 环境变量
* @return 是否成功
*/
@PostMapping(value = "/nodeServer")
public BaseModel<String> save(@RequestBody NodeServer nodeServer, @PathVariable String env) {
nodeServerService.save(nodeServer);
return BaseModel.getInstance("success");
}
/**
* 获取节点信息详情
*
* @param id 节点信息id
* @param env 环境变量
* @return 检点信息
*/
@GetMapping(value = "/nodeServer/{id}")
public BaseModel<NodeServer> detail(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(nodeServerService.detail(id));
}
/**
* 修改节点信息
*
* @param nodeServer 节点信息
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/nodeServer")
public BaseModel<String> update(@RequestBody NodeServer nodeServer, @PathVariable String env) {
nodeServerService.update(nodeServer);
return BaseModel.getInstance("success");
}
/**
* 删除节点信息
*
* @param id 节点信息id
* @param env 环境变量
* @return 是否成功
*/
@DeleteMapping(value = "/nodeServer/{id}")
public BaseModel<String> delete(@PathVariable Long id, @PathVariable String env) {
nodeServerService.delete(id);
return BaseModel.getInstance("success");
}
/**
* 获取远程节点运行状态
*
* @param ip 节点ip
* @param port 节点端口
* @param env 环境变量
* @return 状态信息
*/
@GetMapping(value = "/nodeServer/status")
public BaseModel<Integer> status(@RequestParam String ip, @RequestParam Integer port, @PathVariable String env) {
return BaseModel.getInstance(nodeServerService.remoteNodeStatus(ip, port));
}
/**
* 启动远程节点
*
* @param id 节点id
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/nodeServer/start/{id}")
public BaseModel<Boolean> start(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(nodeServerService.remoteOperation(id, "start"));
}
/**
* 获取远程节点日志
*
* @param id 节点id
* @param env 环境变量
* @return 节点日志
*/
@GetMapping(value = "/nodeServer/log/{id}")
public BaseModel<String> log(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(nodeServerService.remoteCanalLog(id));
}
/**
* 关闭远程节点
*
* @param id 节点id
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/nodeServer/stop/{id}")
public BaseModel<Boolean> stop(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(nodeServerService.remoteOperation(id, "stop"));
}
}

View File

@@ -0,0 +1,114 @@
package com.alibaba.otter.canal.admin.controller;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.CanalConfig;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
import com.alibaba.otter.canal.admin.service.PollingConfigService;
import com.alibaba.otter.canal.protocol.SecurityUtil;
/**
* Canal Instance配置管理控制层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@RestController
@RequestMapping("/api/{env}/config")
public class PollingConfigController {
private static final byte[] seeds = "canal is best!".getBytes();
@Value(value = "${canal.adminUser}")
String user;
@Value(value = "${canal.adminPasswd}")
String passwd;
@Autowired
PollingConfigService pollingConfigService;
/**
* 获取server全局配置
*/
@GetMapping(value = "/server_polling")
public BaseModel<CanalConfig> canalConfigPoll(@RequestHeader String user, @RequestHeader String passwd,
@RequestParam String ip, @RequestParam Integer port,
@RequestParam String md5, @RequestParam boolean register,
@RequestParam String cluster, @PathVariable String env) {
if (!auth(user, passwd)) {
throw new RuntimeException("auth :" + user + " is failed");
}
if (StringUtils.isEmpty(md5) && register) {
// do something
pollingConfigService.autoRegister(ip, port, cluster);
}
CanalConfig canalConfig = pollingConfigService.getChangedConfig(ip, port, md5);
return BaseModel.getInstance(canalConfig);
}
/**
* 获取单个instance的配置
*/
@GetMapping(value = "/instance_polling/{destination}")
public BaseModel<CanalInstanceConfig> instanceConfigPoll(@RequestHeader String user, @RequestHeader String passwd,
@PathVariable String env,
@PathVariable String destination, @RequestParam String md5) {
if (!auth(user, passwd)) {
throw new RuntimeException("auth :" + user + " is failed");
}
CanalInstanceConfig canalInstanceConfig = pollingConfigService.getInstanceConfig(destination, md5);
return BaseModel.getInstance(canalInstanceConfig);
}
/**
* 获取对应server(ip+port)所需要运行的instance列表
*/
@GetMapping(value = "/instances_polling")
public BaseModel<CanalInstanceConfig> instancesPoll(@RequestHeader String user, @RequestHeader String passwd,
@RequestParam String ip, @RequestParam Integer port,
@RequestParam String md5, @PathVariable String env) {
if (!auth(user, passwd)) {
throw new RuntimeException("auth :" + user + " is failed");
}
CanalInstanceConfig canalInstanceConfig = pollingConfigService.getInstancesConfig(ip, port, md5);
return BaseModel.getInstance(canalInstanceConfig);
}
private boolean auth(String user, String passwd) {
// 如果user/passwd密码为空,则任何用户账户都能登录
if ((StringUtils.isEmpty(this.user) || StringUtils.equals(this.user, user))) {
if (StringUtils.isEmpty(this.passwd)) {
return true;
} else if (StringUtils.isEmpty(passwd)) {
// 如果server密码有配置,客户端密码为空,则拒绝
return false;
}
try {
// manager这里保存了原始密码,反过来和canal发送过来的进行校验
byte[] passForClient = SecurityUtil.scramble411(this.passwd.getBytes(), seeds);
return SecurityUtil.scrambleServerAuth(passForClient, SecurityUtil.hexStr2Bytes(passwd), seeds);
} catch (NoSuchAlgorithmException e) {
return false;
}
}
return false;
}
}

View File

@@ -0,0 +1,107 @@
package com.alibaba.otter.canal.admin.controller;
import com.alibaba.otter.canal.admin.service.UserService;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.User;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 用户管理控制层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@RestController
@RequestMapping("/api/{env}/user")
public class UserController {
public static final LoadingCache<String, User> loginUsers = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterAccess(30, TimeUnit.MINUTES)
.build(key -> null); // 用户登录信息缓存
@Autowired
UserService userService;
/**
* 用户登录
*
* @param user 账号密码
* @param env 环境变量
* @return token
*/
@PostMapping(value = "/login")
public BaseModel<Map<String, String>> login(@RequestBody User user, @PathVariable String env) {
User loginUser = userService.find4Login(user.getUsername(), user.getPassword());
if (loginUser != null) {
Map<String, String> tokenResp = new HashMap<>();
String token = UUID.randomUUID().toString();
loginUsers.put(token, loginUser);
tokenResp.put("token", token);
return BaseModel.getInstance(tokenResp);
} else {
BaseModel<Map<String, String>> model = BaseModel.getInstance(null);
model.setCode(40001);
model.setMessage("Invalid username or password");
return model;
}
}
/**
* 获取用户信息
*
* @param token token
* @param env 环境变量
* @return 用户信息
*/
@GetMapping(value = "/info")
public BaseModel<User> info(@RequestParam String token, @PathVariable String env) {
User user = loginUsers.getIfPresent(token);
if (user != null) {
return BaseModel.getInstance(user);
} else {
BaseModel<User> model = BaseModel.getInstance(null);
model.setCode(50014);
model.setMessage("Invalid token");
return model;
}
}
/**
* 修改用户信息
*
* @param user 用户信息
* @param env 环境变量
* @param httpServletRequest httpServletRequest
* @return 是否成功
*/
@PutMapping(value = "")
public BaseModel<String> update(@RequestBody User user, @PathVariable String env,
HttpServletRequest httpServletRequest) {
userService.update(user);
String token = (String) httpServletRequest.getAttribute("token");
loginUsers.put(token, user);
return BaseModel.getInstance("success");
}
/**
* 用户退出
*
* @param env 环境变量
* @return 是否成功
*/
@PostMapping(value = "/logout")
public BaseModel<String> logout(@PathVariable String env) {
return BaseModel.getInstance("success");
}
}

View File

@@ -0,0 +1,44 @@
package com.alibaba.otter.canal.admin.handler;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.admin.model.BaseModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* MVC异常拦截器
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@ControllerAdvice(annotations = ResponseBody.class)
public class CustomExceptionHandler {
private static Logger logger = LoggerFactory.getLogger(CustomExceptionHandler.class);
/**
* 通用异常处理
*
* @param e 异常
* @return
*/
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(value = Exception.class)
public BaseModel commonExceptionHandle(Exception e) {
if (e instanceof ServiceException) {
logger.error(e.getMessage());
} else {
logger.error(e.getMessage(), e);
}
BaseModel res = new BaseModel();
res.setCode(50000);
res.setMessage(e.getMessage());
return res;
}
}

View File

@@ -0,0 +1,44 @@
package com.alibaba.otter.canal.admin.model;
/**
* 基础Model
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
public class BaseModel<T> {
private Integer code = 20000;
private String message;
private T data;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static <T> BaseModel<T> getInstance(T data) {
BaseModel<T> baseModel = new BaseModel<>();
baseModel.data = data;
return baseModel;
}
}

View File

@@ -0,0 +1,70 @@
package com.alibaba.otter.canal.admin.model;
import io.ebean.Finder;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
/**
* Canal集群信息实体类
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Entity
@Table(name = "canal_cluster")
public class CanalCluster extends Model {
public static final CanalClusterFinder find = new CanalClusterFinder();
public static class CanalClusterFinder extends Finder<Long, CanalCluster> {
/**
* Construct using the default EbeanServer.
*/
public CanalClusterFinder(){
super(CanalCluster.class);
}
}
@Id
private Long id;
private String name;
private String zkHosts;
private Date modifiedTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getZkHosts() {
return zkHosts;
}
public void setZkHosts(String zkHosts) {
this.zkHosts = zkHosts;
}
public Date getModifiedTime() {
return modifiedTime;
}
public void setModifiedTime(Date modifiedTime) {
this.modifiedTime = modifiedTime;
}
}

View File

@@ -0,0 +1,110 @@
package com.alibaba.otter.canal.admin.model;
import io.ebean.Finder;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
* Canal主配置实体类
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Entity
public class CanalConfig extends Model {
public static final CanalConfigFinder find = new CanalConfigFinder();
public static class CanalConfigFinder extends Finder<Long, CanalConfig> {
/**
* Construct using the default EbeanServer.
*/
public CanalConfigFinder(){
super(CanalConfig.class);
}
}
@Id
private Long id;
private Long clusterId;
private Long serverId;
private String name;
private String content;
private String contentMd5;
private String status;
private Date modifiedTime;
public void init() {
this.name = "canal.properties";
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getClusterId() {
return clusterId;
}
public void setClusterId(Long clusterId) {
this.clusterId = clusterId;
}
public Long getServerId() {
return serverId;
}
public void setServerId(Long serverId) {
this.serverId = serverId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getContentMd5() {
return contentMd5;
}
public void setContentMd5(String contentMd5) {
this.contentMd5 = contentMd5;
}
public Date getModifiedTime() {
return modifiedTime;
}
public void setModifiedTime(Date modifiedTime) {
this.modifiedTime = modifiedTime;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}

View File

@@ -0,0 +1,153 @@
package com.alibaba.otter.canal.admin.model;
import java.util.Date;
import javax.persistence.*;
import io.ebean.Finder;
/**
* Canal实例配置信息实体类
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Entity
public class CanalInstanceConfig extends Model {
public static final CanalInstanceConfigFinder find = new CanalInstanceConfigFinder();
public static class CanalInstanceConfigFinder extends Finder<Long, CanalInstanceConfig> {
/**
* Construct using the default EbeanServer.
*/
public CanalInstanceConfigFinder(){
super(CanalInstanceConfig.class);
}
}
@Id
private Long id;
@Column(name = "cluster_id")
private Long clusterId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "cluster_id", updatable = false, insertable = false)
private CanalCluster canalCluster;
@Column(name = "server_id")
private Long serverId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "server_id", updatable = false, insertable = false)
private NodeServer nodeServer;
private String name;
private String content;
private String contentMd5;
private String status; // 1: 正常 0: 停止
private Date modifiedTime;
@Transient
private String clusterServerId;
@Transient
private String runningStatus = "0"; // 1: 运行中 0: 停止
public void init() {
status = "1";
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getClusterId() {
return clusterId;
}
public void setClusterId(Long clusterId) {
this.clusterId = clusterId;
}
public CanalCluster getCanalCluster() {
return canalCluster;
}
public void setCanalCluster(CanalCluster canalCluster) {
this.canalCluster = canalCluster;
}
public Long getServerId() {
return serverId;
}
public void setServerId(Long serverId) {
this.serverId = serverId;
}
public NodeServer getNodeServer() {
return nodeServer;
}
public void setNodeServer(NodeServer nodeServer) {
this.nodeServer = nodeServer;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getContentMd5() {
return contentMd5;
}
public void setContentMd5(String contentMd5) {
this.contentMd5 = contentMd5;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Date getModifiedTime() {
return modifiedTime;
}
public void setModifiedTime(Date modifiedTime) {
this.modifiedTime = modifiedTime;
}
public String getClusterServerId() {
return clusterServerId;
}
public void setClusterServerId(String clusterServerId) {
this.clusterServerId = clusterServerId;
}
public String getRunningStatus() {
return runningStatus;
}
public void setRunningStatus(String runningStatus) {
this.runningStatus = runningStatus;
}
}

View File

@@ -0,0 +1,84 @@
package com.alibaba.otter.canal.admin.model;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import java.lang.reflect.Field;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.OptimisticLockException;
import org.apache.commons.beanutils.PropertyUtils;
/**
* EBean Model扩展类
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@MappedSuperclass
public abstract class Model extends io.ebean.Model {
public void init() {
}
public void save() {
init();
super.save();
}
public void insert() {
init();
super.insert();
}
public void saveOrUpdate() {
try {
Field idField = null;
// find id field
Field[] fields = this.getClass().getDeclaredFields();
for (Field field : fields) {
Id idAnn = field.getAnnotation(Id.class);
if (idAnn != null) {
idField = field;
break;
}
}
if (idField == null) {
return;
}
Object idVal = PropertyUtils.getProperty(this, idField.getName());
if (idVal == null) {
this.save();
} else {
this.update();
}
} catch (Exception e) {
throw new OptimisticLockException(e);
}
}
public void update(String... propertiesNames) {
try {
EbeanServer ebeanServer = Ebean.getDefaultServer();
Object id = ebeanServer.getBeanId(this);
Object model = ebeanServer.createQuery(this.getClass()).where().idEq(id).findOne();
for (String propertyName : propertiesNames) {
if (propertyName.startsWith("nn:")) { // not null
propertyName = propertyName.substring(3);
Object val = PropertyUtils.getProperty(this, propertyName);
if (val != null) {
PropertyUtils.setProperty(model, propertyName, val);
}
} else {
Object val = PropertyUtils.getProperty(this, propertyName);
PropertyUtils.setProperty(model, propertyName, val);
}
}
ebeanServer.update(model);
} catch (Exception e) {
throw new OptimisticLockException(e);
}
}
}

View File

@@ -0,0 +1,130 @@
package com.alibaba.otter.canal.admin.model;
import io.ebean.Finder;
import java.util.Date;
import javax.persistence.*;
/**
* 节点信息实体类
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Entity
@Table(name = "canal_node_server")
public class NodeServer extends Model {
public static final NodeServerFinder find = new NodeServerFinder();
public static class NodeServerFinder extends Finder<Long, NodeServer> {
/**
* Construct using the default EbeanServer.
*/
public NodeServerFinder(){
super(NodeServer.class);
}
}
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "cluster_id", updatable = false, insertable = false)
private CanalCluster canalCluster;
@Column(name = "cluster_id")
private Long clusterId;
private String name;
private String ip;
private Integer adminPort;
private Integer metricPort;
private Integer tcpPort;
private String status;
private Date modifiedTime;
public void init() {
status = "-1";
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public CanalCluster getCanalCluster() {
return canalCluster;
}
public void setCanalCluster(CanalCluster canalCluster) {
this.canalCluster = canalCluster;
}
public Long getClusterId() {
return clusterId;
}
public void setClusterId(Long clusterId) {
this.clusterId = clusterId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Integer getAdminPort() {
return adminPort;
}
public void setAdminPort(Integer adminPort) {
this.adminPort = adminPort;
}
public Integer getMetricPort() {
return metricPort;
}
public void setMetricPort(Integer metricPort) {
this.metricPort = metricPort;
}
public Integer getTcpPort() {
return tcpPort;
}
public void setTcpPort(Integer tcpPort) {
this.tcpPort = tcpPort;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Date getModifiedTime() {
return modifiedTime;
}
public void setModifiedTime(Date modifiedTime) {
this.modifiedTime = modifiedTime;
}
}

View File

@@ -0,0 +1,81 @@
package com.alibaba.otter.canal.admin.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class Pager<T> implements Serializable {
private static final long serialVersionUID = -986577815091763517L;
private Long count = 0L;
private List<T> items = new ArrayList<T>();
private Integer page = 1;
private Integer size = 20;
private Long offset = 0L;
public Pager(){
}
public Pager(Integer page, Integer size){
this.page = page;
this.size = size;
}
public Pager(Long count, List<T> items){
this.count = count;
this.items = items;
}
public String toString() {
return "PageResult[count=" + this.count + ", items=" + this.items + "]";
}
public Long getCount() {
return this.count;
}
public void setCount(Long count) {
this.count = count;
}
public List<T> getItems() {
return items;
}
public void setItems(List<T> items) {
this.items = items;
}
public Integer getPage() {
if (page == null) {
page = 1;
}
return page;
}
public void setPage(Integer page) {
this.page = page;
}
public Integer getSize() {
if (size == null) {
size = 20;
}
return size;
}
public void setSize(Integer size) {
this.size = size;
}
public Long getOffset() {
offset = (long) (getPage() - 1) * (long) getSize();
return offset;
}
public void setOffset(Long offset) {
this.offset = offset;
}
}

View File

@@ -0,0 +1,115 @@
package com.alibaba.otter.canal.admin.model;
import io.ebean.Finder;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.util.Date;
/**
* 用户信息实体类
*/
@Entity
@Table(name = "canal_user")
public class User extends Model {
public static final UserFinder find = new UserFinder();
public static class UserFinder extends Finder<Long, User> {
/**
* Construct using the default EbeanServer.
*/
public UserFinder(){
super(User.class);
}
}
@Id
private Long id;
private String username;
private String password;
private String roles;
private String introduction;
private String avatar;
private String name;
private Date creationDate;
@Transient
private String oldPassword;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRoles() {
return roles;
}
public void setRoles(String roles) {
this.roles = roles;
}
public String getIntroduction() {
return introduction;
}
public void setIntroduction(String introduction) {
this.introduction = introduction;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
public String getOldPassword() {
return oldPassword;
}
public void setOldPassword(String oldPassword) {
this.oldPassword = oldPassword;
}
}

View File

@@ -0,0 +1,18 @@
package com.alibaba.otter.canal.admin.service;
import java.util.List;
import com.alibaba.otter.canal.admin.model.CanalCluster;
public interface CanalClusterService {
void save(CanalCluster canalCluster);
CanalCluster detail(Long id);
void update(CanalCluster canalCluster);
void delete(Long id);
List<CanalCluster> findList(CanalCluster canalCluster);
}

View File

@@ -0,0 +1,14 @@
package com.alibaba.otter.canal.admin.service;
import com.alibaba.otter.canal.admin.model.CanalConfig;
public interface CanalConfigService {
CanalConfig getCanalConfig(Long clusterId, Long serverId);
CanalConfig getCanalConfigSummary();
CanalConfig getAdapterConfig();
void updateContent(CanalConfig canalConfig);
}

View File

@@ -0,0 +1,34 @@
package com.alibaba.otter.canal.admin.service;
import java.util.List;
import java.util.Map;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
import com.alibaba.otter.canal.admin.model.Pager;
/**
* Canal实例配置信息业务层接口
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
public interface CanalInstanceService {
Pager<CanalInstanceConfig> findList(CanalInstanceConfig canalInstanceConfig, Pager<CanalInstanceConfig> pager);
void save(CanalInstanceConfig canalInstanceConfig);
CanalInstanceConfig detail(Long id);
void updateContent(CanalInstanceConfig canalInstanceConfig);
void delete(Long id);
Map<String, String> remoteInstanceLog(Long id, Long nodeId);
boolean remoteOperation(Long id, Long nodeId, String option);
boolean instanceOperation(Long id, String option);
List<CanalInstanceConfig> findActiveInstanceByServerId(Long serverId);
}

View File

@@ -0,0 +1,27 @@
package com.alibaba.otter.canal.admin.service;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.admin.model.Pager;
import java.util.List;
public interface NodeServerService {
void save(NodeServer nodeServer);
NodeServer detail(Long id);
void update(NodeServer nodeServer);
void delete(Long id);
List<NodeServer> findAll(NodeServer nodeServer);
Pager<NodeServer> findList(NodeServer nodeServer, Pager<NodeServer> pager);
int remoteNodeStatus(String ip, Integer port);
String remoteCanalLog(Long id);
boolean remoteOperation(Long id, String option);
}

View File

@@ -0,0 +1,15 @@
package com.alibaba.otter.canal.admin.service;
import com.alibaba.otter.canal.admin.model.CanalConfig;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
public interface PollingConfigService {
public boolean autoRegister(String ip, Integer adminPort, String cluster);
CanalConfig getChangedConfig(String ip, Integer port, String md5);
CanalInstanceConfig getInstancesConfig(String ip, Integer port, String md5);
CanalInstanceConfig getInstanceConfig(String destination, String md5);
}

View File

@@ -0,0 +1,10 @@
package com.alibaba.otter.canal.admin.service;
import com.alibaba.otter.canal.admin.model.User;
public interface UserService {
User find4Login(String username, String password);
void update(User user);
}

View File

@@ -0,0 +1,54 @@
package com.alibaba.otter.canal.admin.service.impl;
import io.ebean.Query;
import java.util.List;
import org.springframework.stereotype.Service;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.admin.model.CanalCluster;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.admin.service.CanalClusterService;
@Service
public class CanalClusterServiceImpl implements CanalClusterService {
public void save(CanalCluster canalCluster) {
canalCluster.save();
}
public CanalCluster detail(Long id) {
return CanalCluster.find.byId(id);
}
public void update(CanalCluster canalCluster) {
canalCluster.update("name", "zkHosts");
}
public void delete(Long id) {
// 判断集群下是否存在server信息
int serverCnt = NodeServer.find.query().where().eq("clusterId", id).findCount();
if (serverCnt > 0) {
throw new ServiceException("当前集群下存在Server, 无法删除");
}
// 判断集群下是否存在instance信息
int instanceCnt = CanalInstanceConfig.find.query().where().eq("clusterId", id).findCount();
if (instanceCnt > 0) {
throw new ServiceException("当前集群下存在Instance配置,无法删除");
}
CanalCluster canalCluster = CanalCluster.find.byId(id);
if (canalCluster != null) {
canalCluster.delete();
}
}
public List<CanalCluster> findList(CanalCluster canalCluster) {
Query<CanalCluster> query = CanalCluster.find.query();
query.order().asc("id");
return query.findList();
}
}

View File

@@ -0,0 +1,126 @@
package com.alibaba.otter.canal.admin.service.impl;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.admin.model.CanalConfig;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.admin.service.CanalConfigService;
import com.alibaba.otter.canal.protocol.SecurityUtil;
/**
* Canal配置信息业务层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Service
public class CanalConfigServiceImpl implements CanalConfigService {
private Logger logger = LoggerFactory.getLogger(CanalConfigServiceImpl.class);
private static final String CANAL_GLOBAL_CONFIG = "canal.properties";
private static final String CANAL_ADAPTER_CONFIG = "application.yml";
public CanalConfig getCanalConfig(Long clusterId, Long serverId) {
CanalConfig config = null;
if (clusterId != null && clusterId != 0) {
config = CanalConfig.find.query().where().eq("clusterId", clusterId).findOne();
} else if (serverId != null && serverId != 0) {
config = CanalConfig.find.query().where().eq("serverId", serverId).findOne();
if (config == null) {
NodeServer nodeServer = NodeServer.find.byId(serverId);
if (nodeServer != null) {
Long cid = nodeServer.getClusterId();
if (cid != null) {
config = CanalConfig.find.query().where().eq("clusterId", cid).findOne();
}
}
}
} else {
throw new ServiceException("clusterId and serverId are all empty");
}
if (config == null) {
config = new CanalConfig();
config.setName(CANAL_GLOBAL_CONFIG);
return config;
}
return config;
}
public CanalConfig getCanalConfigSummary() {
return CanalConfig.find.query()
.setDisableLazyLoading(true)
.select("name, modifiedTime")
.where()
.eq("id", 1L)
.findOne();
}
public CanalConfig getAdapterConfig() {
long id = 2L;
CanalConfig config = CanalConfig.find.byId(id);
if (config == null) {
String context = loadDefaultConf(CANAL_ADAPTER_CONFIG);
if (context == null) {
return null;
}
config = new CanalConfig();
config.setId(id);
config.setName(CANAL_ADAPTER_CONFIG);
config.setModifiedTime(new Date());
config.setContent(context);
return config;
}
return config;
}
public void updateContent(CanalConfig canalConfig) {
try {
String contentMd5 = SecurityUtil.md5String(canalConfig.getContent());
canalConfig.setContentMd5(contentMd5);
} catch (NoSuchAlgorithmException e) {
// ignore
}
if (canalConfig.getId() != null) {
CanalConfig canalConfigTmp = CanalConfig.find.byId(canalConfig.getId());
if (canalConfigTmp != null && canalConfigTmp.getClusterId() != null) {
canalConfig.setServerId(null);
}
canalConfig.update("serverId", "content", "contentMd5");
} else {
canalConfig.save();
}
}
private String loadDefaultConf(String confFileName) {
InputStream input = null;
try {
input = Thread.currentThread().getContextClassLoader().getResourceAsStream("conf/" + confFileName);
if (input == null) {
return null;
}
return StringUtils.join(IOUtils.readLines(input), "\n");
} catch (IOException e) {
logger.error("find " + confFileName + " is error!", e);
return null;
} finally {
if (input != null) {
IOUtils.closeQuietly(input);
}
}
}
}

View File

@@ -0,0 +1,320 @@
package com.alibaba.otter.canal.admin.service.impl;
import io.ebean.Query;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import com.alibaba.otter.canal.admin.common.Threads;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.admin.connector.AdminConnector;
import com.alibaba.otter.canal.admin.connector.SimpleAdminConnectors;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.admin.model.Pager;
import com.alibaba.otter.canal.admin.service.CanalInstanceService;
import com.alibaba.otter.canal.protocol.SecurityUtil;
import com.google.common.collect.Lists;
/**
* Canal实例配置信息业务层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Service
public class CanalInstanceServiceImpl implements CanalInstanceService {
public Pager<CanalInstanceConfig> findList(CanalInstanceConfig canalInstanceConfig, Pager<CanalInstanceConfig> pager) {
Query<CanalInstanceConfig> query = CanalInstanceConfig.find.query()
.setDisableLazyLoading(true)
.select("clusterId, serverId, name, modifiedTime")
.fetch("canalCluster", "name")
.fetch("nodeServer", "name,ip,adminPort");
if (canalInstanceConfig != null) {
if (StringUtils.isNotEmpty(canalInstanceConfig.getName())) {
query.where().like("name", "%" + canalInstanceConfig.getName() + "%");
}
if (StringUtils.isNotEmpty(canalInstanceConfig.getClusterServerId())) {
if (canalInstanceConfig.getClusterServerId().startsWith("cluster:")) {
query.where()
.eq("clusterId", Long.parseLong(canalInstanceConfig.getClusterServerId().substring(8)));
} else if (canalInstanceConfig.getClusterServerId().startsWith("server:")) {
query.where().eq("serverId", Long.parseLong(canalInstanceConfig.getClusterServerId().substring(7)));
}
}
}
Query<CanalInstanceConfig> queryCnt = query.copy();
int count = queryCnt.findCount();
pager.setCount((long) count);
query.setFirstRow(pager.getOffset().intValue()).setMaxRows(pager.getSize()).order().asc("id");
List<CanalInstanceConfig> canalInstanceConfigs = query.findList();
pager.setItems(canalInstanceConfigs);
if (canalInstanceConfigs.isEmpty()) {
return pager;
}
// check all canal instances running status
List<Future<Void>> futures = new ArrayList<>(canalInstanceConfigs.size());
for (CanalInstanceConfig canalInstanceConfig1 : canalInstanceConfigs) {
futures.add(Threads.executorService.submit(() -> {
List<NodeServer> nodeServers;
if (canalInstanceConfig1.getClusterId() != null) { // 集群模式
nodeServers = NodeServer.find.query()
.where()
.eq("clusterId", canalInstanceConfig1.getClusterId())
.findList();
} else if (canalInstanceConfig1.getServerId() != null) { // 单机模式
nodeServers = Collections.singletonList(canalInstanceConfig1.getNodeServer());
} else {
return null;
}
for (NodeServer nodeServer : nodeServers) {
String runningInstances = SimpleAdminConnectors.execute(nodeServer.getIp(),
nodeServer.getAdminPort(),
AdminConnector::getRunningInstances);
if (runningInstances == null) {
continue;
}
String[] instances = runningInstances.split(",");
for (String instance : instances) {
if (instance.equals(canalInstanceConfig1.getName())) {
// 集群模式下server对象为空
if (canalInstanceConfig1.getNodeServer() == null) {
canalInstanceConfig1.setNodeServer(nodeServer);
}
canalInstanceConfig1.setRunningStatus("1");
break;
}
}
}
return null;
}));
}
futures.forEach(f -> {
try {
f.get(3, TimeUnit.SECONDS);
} catch (TimeoutException | InterruptedException | ExecutionException e) {
// ignore
}
});
return pager;
}
/**
* 通过Server id获取当前Server下所有运行的Instance
*
* @param serverId server id
*/
public List<CanalInstanceConfig> findActiveInstanceByServerId(Long serverId) {
NodeServer nodeServer = NodeServer.find.byId(serverId);
if (nodeServer == null) {
return null;
}
String runningInstances = SimpleAdminConnectors.execute(nodeServer.getIp(),
nodeServer.getAdminPort(),
AdminConnector::getRunningInstances);
if (runningInstances == null) {
return null;
}
String[] instances = runningInstances.split(",");
Object obj[] = Lists.newArrayList(instances).toArray();
// 单机模式和集群模式区分处理
if (nodeServer.getClusterId() != null) { // 集群模式
List<CanalInstanceConfig> list = CanalInstanceConfig.find.query()
.setDisableLazyLoading(true)
.select("clusterId, serverId, name, modifiedTime")
.where()
// 暂停的实例也显示 .eq("status", "1")
.in("name", obj)
.findList();
list.forEach(config -> config.setRunningStatus("1"));
return list; // 集群模式直接返回当前运行的Instances
} else { // 单机模式
// 当前Server所配置的所有Instance
List<CanalInstanceConfig> list = CanalInstanceConfig.find.query()
.setDisableLazyLoading(true)
.select("clusterId, serverId, name, modifiedTime")
.where()
// 暂停的实例也显示 .eq("status", "1")
.eq("serverId", serverId)
.findList();
List<String> instanceList = Arrays.asList(instances);
list.forEach(config -> {
if (instanceList.contains(config.getName())) {
config.setRunningStatus("1");
}
});
return list;
}
}
public void save(CanalInstanceConfig canalInstanceConfig) {
if (StringUtils.isEmpty(canalInstanceConfig.getClusterServerId())) {
throw new ServiceException("empty cluster or server id");
}
if (canalInstanceConfig.getClusterServerId().startsWith("cluster:")) {
Long clusterId = Long.parseLong(canalInstanceConfig.getClusterServerId().substring(8));
canalInstanceConfig.setClusterId(clusterId);
} else if (canalInstanceConfig.getClusterServerId().startsWith("server:")) {
Long serverId = Long.parseLong(canalInstanceConfig.getClusterServerId().substring(7));
canalInstanceConfig.setServerId(serverId);
}
try {
String contentMd5 = SecurityUtil.md5String(canalInstanceConfig.getContent());
canalInstanceConfig.setContentMd5(contentMd5);
} catch (NoSuchAlgorithmException e) {
// ignore
}
canalInstanceConfig.insert();
}
public CanalInstanceConfig detail(Long id) {
CanalInstanceConfig canalInstanceConfig = CanalInstanceConfig.find.byId(id);
if (canalInstanceConfig != null) {
if (canalInstanceConfig.getClusterId() != null) {
canalInstanceConfig.setClusterServerId("cluster:" + canalInstanceConfig.getClusterId());
} else if (canalInstanceConfig.getServerId() != null) {
canalInstanceConfig.setClusterServerId("server:" + canalInstanceConfig.getServerId());
}
}
return canalInstanceConfig;
}
public void updateContent(CanalInstanceConfig canalInstanceConfig) {
if (StringUtils.isEmpty(canalInstanceConfig.getClusterServerId())) {
throw new ServiceException("empty cluster or server id");
}
if (canalInstanceConfig.getClusterServerId().startsWith("cluster:")) {
Long clusterId = Long.parseLong(canalInstanceConfig.getClusterServerId().substring(8));
canalInstanceConfig.setClusterId(clusterId);
canalInstanceConfig.setServerId(null);
} else if (canalInstanceConfig.getClusterServerId().startsWith("server:")) {
Long serverId = Long.parseLong(canalInstanceConfig.getClusterServerId().substring(7));
canalInstanceConfig.setServerId(serverId);
canalInstanceConfig.setClusterId(null);
}
try {
String contentMd5 = SecurityUtil.md5String(canalInstanceConfig.getContent());
canalInstanceConfig.setContentMd5(contentMd5);
} catch (NoSuchAlgorithmException e) {
// ignore
}
canalInstanceConfig.update("content", "contentMd5", "clusterId", "serverId");
}
public void delete(Long id) {
CanalInstanceConfig canalInstanceConfig = CanalInstanceConfig.find.byId(id);
if (canalInstanceConfig != null) {
canalInstanceConfig.delete();
}
}
public Map<String, String> remoteInstanceLog(Long id, Long nodeId) {
Map<String, String> result = new HashMap<>();
NodeServer nodeServer = NodeServer.find.byId(nodeId);
if (nodeServer == null) {
return result;
}
CanalInstanceConfig canalInstanceConfig = CanalInstanceConfig.find.byId(id);
if (canalInstanceConfig == null) {
return result;
}
String log = SimpleAdminConnectors.execute(nodeServer.getIp(),
nodeServer.getAdminPort(),
adminConnector -> adminConnector.instanceLog(canalInstanceConfig.getName(), null, 100));
result.put("instance", canalInstanceConfig.getName());
result.put("log", log);
return result;
}
public boolean remoteOperation(Long id, Long nodeId, String option) {
NodeServer nodeServer = null;
if ("start".equals(option)) {
if (nodeId != null) {
nodeServer = NodeServer.find.byId(nodeId);
} else {
nodeServer = NodeServer.find.query().findOne();
}
} else {
if (nodeId == null) {
return false;
}
nodeServer = NodeServer.find.byId(nodeId);
}
if (nodeServer == null) {
return false;
}
CanalInstanceConfig canalInstanceConfig = CanalInstanceConfig.find.byId(id);
if (canalInstanceConfig == null) {
return false;
}
Boolean result = null;
if ("start".equals(option)) {
if (nodeServer.getClusterId() == null) { // 非集群模式
return instanceOperation(id, "start");
} else {
throw new ServiceException("集群模式不允许指定server启动");
}
} else if ("stop".equals(option)) {
if (nodeServer.getClusterId() != null) {
// 集群模式,通知主动释放
result = SimpleAdminConnectors.execute(nodeServer.getIp(),
nodeServer.getAdminPort(),
adminConnector -> adminConnector.releaseInstance(canalInstanceConfig.getName()));
} else { // 非集群模式下直接将状态置为0
return instanceOperation(id, "stop");
}
} else {
return false;
}
if (result == null) {
result = false;
}
return result;
}
public boolean instanceOperation(Long id, String option) {
CanalInstanceConfig canalInstanceConfig = CanalInstanceConfig.find.byId(id);
if (canalInstanceConfig == null) {
return false;
}
if ("stop".equals(option)) {
canalInstanceConfig.setStatus("0");
canalInstanceConfig.update("status");
} else if ("start".equals(option)) {
canalInstanceConfig.setStatus("1");
canalInstanceConfig.update("status");
} else {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,202 @@
package com.alibaba.otter.canal.admin.service.impl;
import io.ebean.Query;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import com.alibaba.otter.canal.admin.common.TemplateConfigLoader;
import com.alibaba.otter.canal.admin.common.Threads;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.admin.connector.AdminConnector;
import com.alibaba.otter.canal.admin.connector.SimpleAdminConnectors;
import com.alibaba.otter.canal.admin.model.CanalConfig;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.admin.model.Pager;
import com.alibaba.otter.canal.admin.service.NodeServerService;
import com.alibaba.otter.canal.protocol.SecurityUtil;
/**
* 节点信息业务层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Service
public class NodeServerServiceImpl implements NodeServerService {
public void save(NodeServer nodeServer) {
int cnt = NodeServer.find.query()
.where()
.eq("ip", nodeServer.getIp())
.eq("adminPort", nodeServer.getAdminPort())
.findCount();
if (cnt > 0) {
throw new ServiceException("节点信息已存在");
}
nodeServer.save();
if (nodeServer.getClusterId() == null) { // 单机模式
CanalConfig canalConfig = new CanalConfig();
canalConfig.setServerId(nodeServer.getId());
String configTmp = TemplateConfigLoader.loadCanalConfig();
canalConfig.setContent(configTmp);
try {
String contentMd5 = SecurityUtil.md5String(canalConfig.getContent());
canalConfig.setContentMd5(contentMd5);
} catch (NoSuchAlgorithmException e) {
}
canalConfig.save();
}
}
public NodeServer detail(Long id) {
return NodeServer.find.byId(id);
}
public void update(NodeServer nodeServer) {
int cnt = NodeServer.find.query()
.where()
.eq("ip", nodeServer.getIp())
.eq("adminPort", nodeServer.getAdminPort())
.ne("id", nodeServer.getId())
.findCount();
if (cnt > 0) {
throw new ServiceException("节点信息已存在");
}
nodeServer.update("name", "ip", "adminPort", "tcpPort", "metricPort", "clusterId");
}
public void delete(Long id) {
NodeServer nodeServer = NodeServer.find.byId(id);
if (nodeServer != null) {
// 判断是否存在实例
int cnt = CanalInstanceConfig.find.query().where().eq("serverId", nodeServer.getId()).findCount();
if (cnt > 0) {
throw new ServiceException("当前Server下存在Instance配置, 无法删除");
}
// 同时删除配置
CanalConfig canalConfig = CanalConfig.find.query().where().eq("serverId", id).findOne();
if (canalConfig != null) {
canalConfig.delete();
}
nodeServer.delete();
}
}
private Query<NodeServer> getBaseQuery(NodeServer nodeServer) {
Query<NodeServer> query = NodeServer.find.query();
query.fetch("canalCluster", "name").setDisableLazyLoading(true);
if (nodeServer != null) {
if (StringUtils.isNotEmpty(nodeServer.getName())) {
query.where().like("name", "%" + nodeServer.getName() + "%");
}
if (StringUtils.isNotEmpty(nodeServer.getIp())) {
query.where().eq("ip", nodeServer.getIp());
}
if (nodeServer.getClusterId() != null) {
if (nodeServer.getClusterId() == -1) {
query.where().isNull("clusterId");
} else {
query.where().eq("clusterId", nodeServer.getClusterId());
}
}
}
return query;
}
public List<NodeServer> findAll(NodeServer nodeServer) {
Query<NodeServer> query = getBaseQuery(nodeServer);
query.order().asc("id");
return query.findList();
}
public Pager<NodeServer> findList(NodeServer nodeServer, Pager<NodeServer> pager) {
Query<NodeServer> query = getBaseQuery(nodeServer);
Query<NodeServer> queryCnt = query.copy();
int count = queryCnt.findCount();
pager.setCount((long) count);
List<NodeServer> nodeServers = query.order()
.asc("id")
.setFirstRow(pager.getOffset().intValue())
.setMaxRows(pager.getSize())
.findList();
pager.setItems(nodeServers);
if (nodeServers.isEmpty()) {
return pager;
}
List<Future<Boolean>> futures = new ArrayList<>(nodeServers.size());
// get all nodes status
for (NodeServer ns : nodeServers) {
futures.add(Threads.executorService.submit(() -> {
boolean status = SimpleAdminConnectors.execute(ns.getIp(), ns.getAdminPort(), AdminConnector::check);
ns.setStatus(status ? "1" : "0");
return !status;
}));
}
futures.forEach(f -> {
try {
f.get(3, TimeUnit.SECONDS);
} catch (TimeoutException | InterruptedException | ExecutionException e) {
// ignore
}
});
return pager;
}
public int remoteNodeStatus(String ip, Integer port) {
boolean result = SimpleAdminConnectors.execute(ip, port, AdminConnector::check);
return result ? 1 : 0;
}
public String remoteCanalLog(Long id) {
NodeServer nodeServer = NodeServer.find.byId(id);
if (nodeServer == null) {
return "";
}
return SimpleAdminConnectors.execute(nodeServer.getIp(),
nodeServer.getAdminPort(),
adminConnector -> adminConnector.canalLog(100));
}
public boolean remoteOperation(Long id, String option) {
NodeServer nodeServer = NodeServer.find.byId(id);
if (nodeServer == null) {
return false;
}
Boolean result = null;
if ("start".equals(option)) {
result = SimpleAdminConnectors.execute(nodeServer.getIp(), nodeServer.getAdminPort(), AdminConnector::start);
} else if ("stop".equals(option)) {
result = SimpleAdminConnectors.execute(nodeServer.getIp(), nodeServer.getAdminPort(), AdminConnector::stop);
} else {
return false;
}
if (result == null) {
result = false;
}
return result;
}
}

View File

@@ -0,0 +1,134 @@
package com.alibaba.otter.canal.admin.service.impl;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.admin.model.CanalCluster;
import com.alibaba.otter.canal.admin.model.CanalConfig;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
import com.alibaba.otter.canal.admin.model.NodeServer;
import com.alibaba.otter.canal.admin.service.CanalClusterService;
import com.alibaba.otter.canal.admin.service.NodeServerService;
import com.alibaba.otter.canal.admin.service.PollingConfigService;
import com.alibaba.otter.canal.protocol.SecurityUtil;
import com.google.common.base.Joiner;
@Service
public class PollingConfigServiceImpl implements PollingConfigService {
@Autowired
NodeServerService nodeServerService;
@Autowired
CanalClusterService canalClusterService;
public boolean autoRegister(String ip, Integer adminPort, String cluster) {
NodeServer server = NodeServer.find.query().where().eq("ip", ip).eq("adminPort", adminPort).findOne();
if (server == null) {
server = new NodeServer();
server.setName(ip);
server.setIp(ip);
server.setAdminPort(adminPort);
server.setTcpPort(adminPort + 1);
server.setMetricPort(adminPort + 2);
if (StringUtils.isNotEmpty(cluster)) {
CanalCluster clusterConfig = CanalCluster.find.query().where().eq("name", cluster).findOne();
if (clusterConfig == null) {
throw new ServiceException("auto cluster : " + cluster + " is not found.");
}
server.setClusterId(clusterConfig.getId());
}
nodeServerService.save(server);
}
return true;
}
public CanalConfig getChangedConfig(String ip, Integer port, String md5) {
NodeServer server = NodeServer.find.query().where().eq("ip", ip).eq("adminPort", port).findOne();
if (server == null) {
return null;
}
CanalConfig canalConfig;
if (server.getClusterId() != null) { // 集群模式
canalConfig = CanalConfig.find.query().where().eq("clusterId", server.getClusterId()).findOne();
} else { // 单机模式
canalConfig = CanalConfig.find.query().where().eq("serverId", server.getId()).findOne();
}
if (canalConfig == null) {
throw new ServiceException("canal.properties config is empty");
}
if (!canalConfig.getContentMd5().equals(md5)) { // 内容发生变化
return canalConfig;
}
return null;
}
public CanalInstanceConfig getInstancesConfig(String ip, Integer port, String md5) {
NodeServer server = NodeServer.find.query().where().eq("ip", ip).eq("adminPort", port).findOne();
if (server == null) {
return null;
}
List<CanalInstanceConfig> canalInstanceConfigs;
if (server.getClusterId() != null) { // 集群模式
canalInstanceConfigs = CanalInstanceConfig.find.query()
.where()
.eq("status", "1")
.eq("clusterId", server.getClusterId())
.findList(); // 取属于该集群的所有instance config
} else { // 单机模式
canalInstanceConfigs = CanalInstanceConfig.find.query()
.where()
.eq("status", "1")
.eq("serverId", server.getId())
.findList();
}
CanalInstanceConfig canalInstanceConfig = new CanalInstanceConfig();
List<String> instances = canalInstanceConfigs.stream()
.map(CanalInstanceConfig::getName)
.collect(Collectors.toList());
String data = Joiner.on(',').join(instances);
canalInstanceConfig.setContent(data);
if (!StringUtils.isEmpty(md5)) {
try {
String newMd5 = SecurityUtil.md5String(canalInstanceConfig.getContent());
if (StringUtils.equals(md5, newMd5)) {
canalInstanceConfig.setContent(null);
}
} catch (NoSuchAlgorithmException e) {
// ignore
}
}
return canalInstanceConfig;
}
public CanalInstanceConfig getInstanceConfig(String destination, String md5) {
CanalInstanceConfig instanceConfig = CanalInstanceConfig.find.query().where().eq("name", destination).findOne();
if (instanceConfig == null) {
return null;
}
if (StringUtils.isEmpty(md5)) {
return instanceConfig;
} else {
try {
String newMd5 = SecurityUtil.md5String(instanceConfig.getContent());
if (StringUtils.equals(md5, newMd5)) {
instanceConfig.setContent(null);
}
} catch (NoSuchAlgorithmException e) {
// ignore
}
return instanceConfig;
}
}
}

View File

@@ -0,0 +1,65 @@
package com.alibaba.otter.canal.admin.service.impl;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import com.alibaba.otter.canal.admin.common.exception.ServiceException;
import com.alibaba.otter.canal.admin.model.User;
import com.alibaba.otter.canal.admin.service.UserService;
import com.alibaba.otter.canal.protocol.SecurityUtil;
/**
* 用户信息业务层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Service
public class UserServiceImpl implements UserService {
private static byte[] seeds = "canal is best!".getBytes();
public User find4Login(String username, String password) {
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
return null;
}
User user = User.find.query().where().eq("username", username).findOne();
if (user == null) {
throw new ServiceException("user:" + username + " auth failed!");
}
try {
byte[] pass = SecurityUtil.scramble411(password.getBytes(), seeds);
if (!SecurityUtil.scrambleServerAuth(pass, SecurityUtil.hexStr2Bytes(user.getPassword()), seeds)) {
throw new ServiceException("user:" + user.getName() + " passwd incorrect!");
}
} catch (NoSuchAlgorithmException e) {
throw new ServiceException("user:" + user.getName() + " auth failed!");
}
user.setPassword("");
return user;
}
public void update(User user) {
User userTmp = User.find.query().where().eq("username", user.getUsername()).findOne();
if (userTmp == null) {
throw new ServiceException();
}
try {
byte[] pass = SecurityUtil.scramble411(user.getOldPassword().getBytes(), seeds);
if (!SecurityUtil.scrambleServerAuth(pass, SecurityUtil.hexStr2Bytes(userTmp.getPassword()), seeds)) {
throw new ServiceException("old passwd is unmatch");
}
user.setId(userTmp.getId());
user.setPassword(SecurityUtil.scrambleGenPass(user.getPassword().getBytes()));
} catch (NoSuchAlgorithmException e) {
throw new ServiceException("passwd process failed");
}
user.update("username", "nn:password");
}
}

View File

@@ -0,0 +1,21 @@
server:
port: 8089
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
spring.datasource:
address: 127.0.0.1:3306
database: canal_manager
username: canal
password: canal
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://${spring.datasource.address}/${spring.datasource.database}?useUnicode=true&characterEncoding=UTF-8&useSSL=false
hikari:
maximum-pool-size: 30
minimum-idle: 1
canal:
adminUser: admin
adminPasswd: admin

View File

@@ -0,0 +1,139 @@
#################################################
######### common argument #############
#################################################
# tcp bind ip
canal.ip =
# register ip to zookeeper
canal.register.ip =
canal.port = 11111
canal.metrics.pull.port = 11112
# canal instance user/passwd
canal.user = canal
canal.passwd = E3619321C1A937C46A0D8BD1DAC39F93B27D4458
# canal admin config
canal.admin.manager = 127.0.0.1:8089
canal.admin.port = 11110
canal.admin.user = admin
canal.admin.passwd = 4ACFE3202A5FF5CF467898FC58AAB1D615029441
canal.zkServers =
# flush data to zk
canal.zookeeper.flush.period = 1000
canal.withoutNetty = false
# tcp, kafka, RocketMQ
canal.serverMode = tcp
# flush meta cursor/parse position to file
canal.file.data.dir = ${canal.conf.dir}
canal.file.flush.period = 1000
## memory store RingBuffer size, should be Math.pow(2,n)
canal.instance.memory.buffer.size = 16384
## memory store RingBuffer used memory unit size , default 1kb
canal.instance.memory.buffer.memunit = 1024
## meory store gets mode used MEMSIZE or ITEMSIZE
canal.instance.memory.batch.mode = MEMSIZE
canal.instance.memory.rawEntry = true
## detecing config
canal.instance.detecting.enable = false
#canal.instance.detecting.sql = insert into retl.xdual values(1,now()) on duplicate key update x=now()
canal.instance.detecting.sql = select 1
canal.instance.detecting.interval.time = 3
canal.instance.detecting.retry.threshold = 3
canal.instance.detecting.heartbeatHaEnable = false
# support maximum transaction size, more than the size of the transaction will be cut into multiple transactions delivery
canal.instance.transaction.size = 1024
# mysql fallback connected to new master should fallback times
canal.instance.fallbackIntervalInSeconds = 60
# network config
canal.instance.network.receiveBufferSize = 16384
canal.instance.network.sendBufferSize = 16384
canal.instance.network.soTimeout = 30
# binlog filter config
canal.instance.filter.druid.ddl = true
canal.instance.filter.query.dcl = false
canal.instance.filter.query.dml = false
canal.instance.filter.query.ddl = false
canal.instance.filter.table.error = false
canal.instance.filter.rows = false
canal.instance.filter.transaction.entry = false
# binlog format/image check
canal.instance.binlog.format = ROW,STATEMENT,MIXED
canal.instance.binlog.image = FULL,MINIMAL,NOBLOB
# binlog ddl isolation
canal.instance.get.ddl.isolation = false
# parallel parser config
canal.instance.parser.parallel = true
## concurrent thread number, default 60% available processors, suggest not to exceed Runtime.getRuntime().availableProcessors()
#canal.instance.parser.parallelThreadSize = 16
## disruptor ringbuffer size, must be power of 2
canal.instance.parser.parallelBufferSize = 256
# table meta tsdb info
canal.instance.tsdb.enable = true
canal.instance.tsdb.dir = ${canal.file.data.dir:../conf}/${canal.instance.destination:}
canal.instance.tsdb.url = jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL;
canal.instance.tsdb.dbUsername = canal
canal.instance.tsdb.dbPassword = canal
# dump snapshot interval, default 24 hour
canal.instance.tsdb.snapshot.interval = 24
# purge snapshot expire , default 360 hour(15 days)
canal.instance.tsdb.snapshot.expire = 360
# aliyun ak/sk , support rds/mq
canal.aliyun.accessKey =
canal.aliyun.secretKey =
#################################################
######### destinations #############
#################################################
canal.destinations =
# conf root dir
canal.conf.dir = ../conf
# auto scan instance dir add/remove and start/stop instance
canal.auto.scan = true
canal.auto.scan.interval = 5
canal.instance.tsdb.spring.xml = classpath:spring/tsdb/h2-tsdb.xml
#canal.instance.tsdb.spring.xml = classpath:spring/tsdb/mysql-tsdb.xml
canal.instance.global.mode = manager
canal.instance.global.lazy = false
canal.instance.global.manager.address = ${canal.admin.manager}
#canal.instance.global.spring.xml = classpath:spring/memory-instance.xml
canal.instance.global.spring.xml = classpath:spring/file-instance.xml
#canal.instance.global.spring.xml = classpath:spring/default-instance.xml
##################################################
######### MQ #############
##################################################
canal.mq.servers = 127.0.0.1:6667
canal.mq.retries = 0
canal.mq.batchSize = 16384
canal.mq.maxRequestSize = 1048576
canal.mq.lingerMs = 100
canal.mq.bufferMemory = 33554432
canal.mq.canalBatchSize = 50
canal.mq.canalGetTimeout = 100
canal.mq.flatMessage = true
canal.mq.compressionType = none
canal.mq.acks = all
#canal.mq.properties. =
canal.mq.producerGroup = test
# Set this value to "cloud", if you want open message trace feature in aliyun.
canal.mq.accessChannel = local
# aliyun mq namespace
#canal.mq.namespace =
##################################################
######### Kafka Kerberos Info #############
##################################################
canal.mq.kafka.kerberos.enable = false
canal.mq.kafka.kerberos.krb5FilePath = "../conf/kerberos/krb5.conf"
canal.mq.kafka.kerberos.jaasFilePath = "../conf/kerberos/jaas.conf"

View File

@@ -0,0 +1,110 @@
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;

View File

@@ -0,0 +1,57 @@
#################################################
## mysql serverId , v1.0.26+ will autoGen
# canal.instance.mysql.slaveId=0
# enable gtid use true/false
canal.instance.gtidon=false
# position info
canal.instance.master.address=127.0.0.1:3306
canal.instance.master.journal.name=
canal.instance.master.position=
canal.instance.master.timestamp=
canal.instance.master.gtid=
# rds oss binlog
canal.instance.rds.accesskey=
canal.instance.rds.secretkey=
canal.instance.rds.instanceId=
# table meta tsdb info
canal.instance.tsdb.enable=true
#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
#canal.instance.tsdb.dbUsername=canal
#canal.instance.tsdb.dbPassword=canal
#canal.instance.standby.address =
#canal.instance.standby.journal.name =
#canal.instance.standby.position =
#canal.instance.standby.timestamp =
#canal.instance.standby.gtid=
# username/password
canal.instance.dbUsername=canal
canal.instance.dbPassword=canal
canal.instance.connectionCharset = UTF-8
# enable druid Decrypt database password
canal.instance.enableDruid=false
#canal.instance.pwdPublicKey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALK4BUxdDltRRE5/zXpVEVPUgunvscYFtEip3pmLlhrWpacX7y7GCMo2/JM6LeHmiiNdH1FWgGCpUfircSwlWKUCAwEAAQ==
# table regex
canal.instance.filter.regex=.*\\..*
# table black regex
canal.instance.filter.black.regex=
# table field filter(format: schema1.tableName1:field1/field2,schema2.tableName2:field1/field2)
#canal.instance.filter.field=test1.t_product:id/subject/keywords,test2.t_company:id/name/contact/ch
# table field black filter(format: schema1.tableName1:field1/field2,schema2.tableName2:field1/field2)
#canal.instance.filter.black.field=test1.t_product:subject/product_image,test2.t_company:id/name/contact/ch
# mq config
canal.mq.topic=example
# dynamic topic route by schema or table regex
#canal.mq.dynamicTopic=mytest1.user,mytest2\\..*,.*\\..*
canal.mq.partition=0
# hash partition config
#canal.mq.partitionsNum=3
#canal.mq.partitionHash=test.table:id^name,.*\\..*
#################################################

View File

@@ -0,0 +1,47 @@
<configuration scan="true" scanPeriod=" 5 seconds">
<jmxConfigurator />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n
</pattern>
</encoder>
</appender>
<appender name="CANAL-ROOT" class="ch.qos.logback.classic.sift.SiftingAppender">
<discriminator>
<Key>admin</Key>
<DefaultValue>admin</DefaultValue>
</discriminator>
<sift>
<appender name="FILE-admin" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>../logs/admin.log</File>
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>../logs/admin/%d{yyyy-MM-dd}/admin-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- or whenever the file size reaches 100MB -->
<maxFileSize>512MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n
</pattern>
</encoder>
</appender>
</sift>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="CANAL-ROOT" />
</root>
<logger name="io.ebean.SQL" additivity="false">
<level value="INFO" />
<appender-ref ref="STDOUT"/>
<appender-ref ref="CANAL-ROOT" />
</logger>
</configuration>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -0,0 +1 @@
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=/favicon.ico><title>Canal Admin</title><link href=/static/css/chunk-elementUI.18b11d0e.css rel=stylesheet><link href=/static/css/chunk-libs.5cf311f0.css rel=stylesheet><link href=/static/css/app.bb951cb3.css rel=stylesheet></head><body><noscript><strong>We're sorry but Canal Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/static/js/chunk-elementUI.667f4c87.js></script><script src=/static/js/chunk-libs.c04beefc.js></script><script>(function(e){function n(n){for(var c,r,o=n[0],f=n[1],i=n[2],d=0,h=[];d<o.length;d++)r=o[d],u[r]&&h.push(u[r][0]),u[r]=0;for(c in f)Object.prototype.hasOwnProperty.call(f,c)&&(e[c]=f[c]);l&&l(n);while(h.length)h.shift()();return a.push.apply(a,i||[]),t()}function t(){for(var e,n=0;n<a.length;n++){for(var t=a[n],c=!0,r=1;r<t.length;r++){var o=t[r];0!==u[o]&&(c=!1)}c&&(a.splice(n--,1),e=f(f.s=t[0]))}return e}var c={},r={runtime:0},u={runtime:0},a=[];function o(e){return f.p+"static/js/"+({}[e]||e)+"."+{"chunk-0dca2f22":"a2bc28b8","chunk-5afa45f5":"79ddcc04","chunk-5b373aad":"90a2d8e7","chunk-bd1d44ee":"8e2c4851","chunk-101fc062":"bc898027","chunk-2b9b6c5c":"cc2181b9","chunk-37c49cbf":"64d26540","chunk-49959c8b":"6d226f70","chunk-4f09fed2":"ff28d88d","chunk-55380ff2":"681c71c9","chunk-69386cf0":"76d77f5c","chunk-7ec889b7":"bd1ca803","chunk-da289616":"7def925e"}[e]+".js"}function f(n){if(c[n])return c[n].exports;var t=c[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.e=function(e){var n=[],t={"chunk-5afa45f5":1,"chunk-5b373aad":1,"chunk-bd1d44ee":1,"chunk-101fc062":1,"chunk-2b9b6c5c":1,"chunk-37c49cbf":1,"chunk-49959c8b":1,"chunk-4f09fed2":1,"chunk-69386cf0":1,"chunk-7ec889b7":1,"chunk-da289616":1};r[e]?n.push(r[e]):0!==r[e]&&t[e]&&n.push(r[e]=new Promise(function(n,t){for(var c="static/css/"+({}[e]||e)+"."+{"chunk-0dca2f22":"31d6cfe0","chunk-5afa45f5":"a34bb177","chunk-5b373aad":"b966d36b","chunk-bd1d44ee":"1528199a","chunk-101fc062":"fad9926f","chunk-2b9b6c5c":"c89f8844","chunk-37c49cbf":"efc21a9c","chunk-49959c8b":"e8e2beee","chunk-4f09fed2":"70ec0b86","chunk-55380ff2":"31d6cfe0","chunk-69386cf0":"741ff14e","chunk-7ec889b7":"c0585512","chunk-da289616":"c89f8844"}[e]+".css",u=f.p+c,a=document.getElementsByTagName("link"),o=0;o<a.length;o++){var i=a[o],d=i.getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(d===c||d===u))return n()}var h=document.getElementsByTagName("style");for(o=0;o<h.length;o++){i=h[o],d=i.getAttribute("data-href");if(d===c||d===u)return n()}var l=document.createElement("link");l.rel="stylesheet",l.type="text/css",l.onload=n,l.onerror=function(n){var c=n&&n.target&&n.target.src||u,a=new Error("Loading CSS chunk "+e+" failed.\n("+c+")");a.code="CSS_CHUNK_LOAD_FAILED",a.request=c,delete r[e],l.parentNode.removeChild(l),t(a)},l.href=u;var s=document.getElementsByTagName("head")[0];s.appendChild(l)}).then(function(){r[e]=0}));var c=u[e];if(0!==c)if(c)n.push(c[2]);else{var a=new Promise(function(n,t){c=u[e]=[n,t]});n.push(c[2]=a);var i,d=document.createElement("script");d.charset="utf-8",d.timeout=120,f.nc&&d.setAttribute("nonce",f.nc),d.src=o(e),i=function(n){d.onerror=d.onload=null,clearTimeout(h);var t=u[e];if(0!==t){if(t){var c=n&&("load"===n.type?"missing":n.type),r=n&&n.target&&n.target.src,a=new Error("Loading chunk "+e+" failed.\n("+c+": "+r+")");a.type=c,a.request=r,t[1](a)}u[e]=void 0}};var h=setTimeout(function(){i({type:"timeout",target:d})},12e4);d.onerror=d.onload=i,document.head.appendChild(d)}return Promise.all(n)},f.m=e,f.c=c,f.d=function(e,n,t){f.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,n){if(1&n&&(e=f(e)),8&n)return e;if(4&n&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var c in e)f.d(t,c,function(n){return e[n]}.bind(null,c));return t},f.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return f.d(n,"a",n),n},f.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},f.p="/",f.oe=function(e){throw console.error(e),e};var i=window["webpackJsonp"]=window["webpackJsonp"]||[],d=i.push.bind(i);i.push=n,i=i.slice();for(var h=0;h<i.length;h++)n(i[h]);var l=d;t()})([]);</script><script src=/static/js/app.6845b228.js></script></body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
@supports (-webkit-mask:none) and (not (cater-color:#fff)){.login-container .el-input input{color:#fff}}.login-container .el-input{display:inline-block;height:47px;width:85%}.login-container .el-input input{background:transparent;border:0;-webkit-appearance:none;border-radius:0;padding:12px 5px 12px 15px;color:#fff;height:47px;caret-color:#fff}.login-container .el-input input:-webkit-autofill{-webkit-box-shadow:0 0 0 1000px #283443 inset!important;box-shadow:inset 0 0 0 1000px #283443!important;-webkit-text-fill-color:#fff!important}.login-container .el-form-item{border:1px solid hsla(0,0%,100%,.1);background:rgba(0,0,0,.1);border-radius:5px;color:#454545}.login-container[data-v-31c14ebf]{min-height:100%;width:100%;background-color:#2d3a4b;overflow:hidden}.login-container .login-form[data-v-31c14ebf]{position:relative;width:520px;max-width:100%;padding:160px 35px 0;margin:0 auto;overflow:hidden}.login-container .tips[data-v-31c14ebf]{font-size:14px;color:#fff;margin-bottom:10px}.login-container .tips span[data-v-31c14ebf]:first-of-type{margin-right:16px}.login-container .svg-container[data-v-31c14ebf]{padding:6px 5px 6px 15px;color:#889aa4;vertical-align:middle;width:30px;display:inline-block}.login-container .title-container[data-v-31c14ebf]{position:relative}.login-container .title-container .title[data-v-31c14ebf]{font-size:26px;color:#eee;margin:0 auto 40px auto;text-align:center;font-weight:700}.login-container .show-pwd[data-v-31c14ebf]{position:absolute;right:10px;top:7px;font-size:16px;color:#889aa4;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}

View File

@@ -0,0 +1 @@
.pagination-container[data-v-cebf2f0c]{background:#fff;padding:32px 16px}.pagination-container.hidden[data-v-cebf2f0c]{display:none}

View File

@@ -0,0 +1 @@
.pagination-container[data-v-cebf2f0c]{background:#fff;padding:32px 16px}.pagination-container.hidden[data-v-cebf2f0c]{display:none}

View File

@@ -0,0 +1 @@
.line[data-v-28f0cd0f]{text-align:center}

View File

@@ -0,0 +1 @@
.pagination-container[data-v-38ef71f0]{background:#fff;padding:32px 16px}.pagination-container.hidden[data-v-38ef71f0]{display:none}

View File

@@ -0,0 +1 @@
.dashboard-container[data-v-42037c2b]{margin:30px}.dashboard-text[data-v-42037c2b]{font-size:30px;line-height:46px}

View File

@@ -0,0 +1 @@
.wscn-http404-container[data-v-c095f994]{-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);position:absolute;top:40%;left:50%}.wscn-http404[data-v-c095f994]{position:relative;width:1200px;padding:0 50px;overflow:hidden}.wscn-http404 .pic-404[data-v-c095f994]{position:relative;float:left;width:600px;overflow:hidden}.wscn-http404 .pic-404__parent[data-v-c095f994]{width:100%}.wscn-http404 .pic-404__child[data-v-c095f994]{position:absolute}.wscn-http404 .pic-404__child.left[data-v-c095f994]{width:80px;top:17px;left:220px;opacity:0;-webkit-animation-name:cloudLeft-data-v-c095f994;animation-name:cloudLeft-data-v-c095f994;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1s;animation-delay:1s}.wscn-http404 .pic-404__child.mid[data-v-c095f994]{width:46px;top:10px;left:420px;opacity:0;-webkit-animation-name:cloudMid-data-v-c095f994;animation-name:cloudMid-data-v-c095f994;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1.2s;animation-delay:1.2s}.wscn-http404 .pic-404__child.right[data-v-c095f994]{width:62px;top:100px;left:500px;opacity:0;-webkit-animation-name:cloudRight-data-v-c095f994;animation-name:cloudRight-data-v-c095f994;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1s;animation-delay:1s}@-webkit-keyframes cloudLeft-data-v-c095f994{0%{top:17px;left:220px;opacity:0}20%{top:33px;left:188px;opacity:1}80%{top:81px;left:92px;opacity:1}to{top:97px;left:60px;opacity:0}}@keyframes cloudLeft-data-v-c095f994{0%{top:17px;left:220px;opacity:0}20%{top:33px;left:188px;opacity:1}80%{top:81px;left:92px;opacity:1}to{top:97px;left:60px;opacity:0}}@-webkit-keyframes cloudMid-data-v-c095f994{0%{top:10px;left:420px;opacity:0}20%{top:40px;left:360px;opacity:1}70%{top:130px;left:180px;opacity:1}to{top:160px;left:120px;opacity:0}}@keyframes cloudMid-data-v-c095f994{0%{top:10px;left:420px;opacity:0}20%{top:40px;left:360px;opacity:1}70%{top:130px;left:180px;opacity:1}to{top:160px;left:120px;opacity:0}}@-webkit-keyframes cloudRight-data-v-c095f994{0%{top:100px;left:500px;opacity:0}20%{top:120px;left:460px;opacity:1}80%{top:180px;left:340px;opacity:1}to{top:200px;left:300px;opacity:0}}@keyframes cloudRight-data-v-c095f994{0%{top:100px;left:500px;opacity:0}20%{top:120px;left:460px;opacity:1}80%{top:180px;left:340px;opacity:1}to{top:200px;left:300px;opacity:0}}.wscn-http404 .bullshit[data-v-c095f994]{position:relative;float:left;width:300px;padding:30px 0;overflow:hidden}.wscn-http404 .bullshit__oops[data-v-c095f994]{font-size:32px;line-height:40px;color:#1482f0;margin-bottom:20px;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__headline[data-v-c095f994],.wscn-http404 .bullshit__oops[data-v-c095f994]{font-weight:700;opacity:0;-webkit-animation-name:slideUp-data-v-c095f994;animation-name:slideUp-data-v-c095f994;-webkit-animation-duration:.5s;animation-duration:.5s}.wscn-http404 .bullshit__headline[data-v-c095f994]{font-size:20px;line-height:24px;color:#222;margin-bottom:10px;-webkit-animation-delay:.1s;animation-delay:.1s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__info[data-v-c095f994]{font-size:13px;line-height:21px;color:grey;margin-bottom:30px;-webkit-animation-delay:.2s;animation-delay:.2s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__info[data-v-c095f994],.wscn-http404 .bullshit__return-home[data-v-c095f994]{opacity:0;-webkit-animation-name:slideUp-data-v-c095f994;animation-name:slideUp-data-v-c095f994;-webkit-animation-duration:.5s;animation-duration:.5s}.wscn-http404 .bullshit__return-home[data-v-c095f994]{display:block;float:left;width:110px;height:36px;background:#1482f0;border-radius:100px;text-align:center;color:#fff;font-size:14px;line-height:36px;cursor:pointer;-webkit-animation-delay:.3s;animation-delay:.3s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}@-webkit-keyframes slideUp-data-v-c095f994{0%{-webkit-transform:translateY(60px);transform:translateY(60px);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}@keyframes slideUp-data-v-c095f994{0%{-webkit-transform:translateY(60px);transform:translateY(60px);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}

View File

@@ -0,0 +1 @@
.line[data-v-64c3fae5]{text-align:center}

View File

@@ -0,0 +1 @@
.line[data-v-2e6a6604]{text-align:center}

View File

@@ -0,0 +1 @@
.line[data-v-e884438a]{text-align:center}

View File

@@ -0,0 +1 @@
.line[data-v-756ebb70]{text-align:center}

View File

@@ -0,0 +1 @@
.line[data-v-0b80198c]{text-align:center}

View File

@@ -0,0 +1 @@
.line[data-v-35af5ff9]{text-align:center}

View File

@@ -0,0 +1 @@
.line[data-v-5c332416]{text-align:center}

View File

@@ -0,0 +1 @@
.pagination-container[data-v-38ef71f0]{background:#fff;padding:32px 16px}.pagination-container.hidden[data-v-38ef71f0]{display:none}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit;font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}[hidden],template{display:none}#nprogress{pointer-events:none}#nprogress .bar{background:#29d;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;-webkit-box-shadow:0 0 10px #29d,0 0 5px #29d;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1;-webkit-transform:rotate(3deg) translateY(-4px);transform:rotate(3deg) translateY(-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;-webkit-box-sizing:border-box;box-sizing:border-box;border:2px solid transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;-webkit-animation:nprogress-spinner .4s linear infinite;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@-webkit-keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(1turn)}}@keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-101fc062"],{2017:function(e,t,s){"use strict";var n=s("3b76"),o=s.n(n);o.a},"3b76":function(e,t,s){},"562b":function(e,t,s){},"9ed6":function(e,t,s){"use strict";s.r(t);var n=function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("div",{staticClass:"login-container"},[s("el-form",{ref:"loginForm",staticClass:"login-form",attrs:{model:e.loginForm,rules:e.loginRules,"auto-complete":"on","label-position":"left"}},[s("div",{staticClass:"title-container"},[s("h3",{staticClass:"title"},[e._v("Canal Admin Login")])]),e._v(" "),s("el-form-item",{attrs:{prop:"username"}},[s("span",{staticClass:"svg-container"},[s("svg-icon",{attrs:{"icon-class":"user"}})],1),e._v(" "),s("el-input",{ref:"username",attrs:{placeholder:"Username",name:"username",type:"text",tabindex:"1","auto-complete":"on"},model:{value:e.loginForm.username,callback:function(t){e.$set(e.loginForm,"username",t)},expression:"loginForm.username"}})],1),e._v(" "),s("el-form-item",{attrs:{prop:"password"}},[s("span",{staticClass:"svg-container"},[s("svg-icon",{attrs:{"icon-class":"password"}})],1),e._v(" "),s("el-input",{key:e.passwordType,ref:"password",attrs:{type:e.passwordType,placeholder:"Password",name:"password",tabindex:"2","auto-complete":"on"},nativeOn:{keyup:function(t){return!t.type.indexOf("key")&&e._k(t.keyCode,"enter",13,t.key,"Enter")?null:e.handleLogin(t)}},model:{value:e.loginForm.password,callback:function(t){e.$set(e.loginForm,"password",t)},expression:"loginForm.password"}}),e._v(" "),s("span",{staticClass:"show-pwd",on:{click:e.showPwd}},[s("svg-icon",{attrs:{"icon-class":"password"===e.passwordType?"eye":"eye-open"}})],1)],1),e._v(" "),s("el-button",{staticStyle:{width:"100%","margin-bottom":"30px"},attrs:{loading:e.loading,type:"primary"},nativeOn:{click:function(t){return t.preventDefault(),e.handleLogin(t)}}},[e._v("Login")]),e._v(" "),s("div",{staticClass:"tips"})],1)],1)},o=[],r=s("61f7"),a={name:"Login",data:function(){var e=function(e,t,s){Object(r["b"])(t)?s():s(new Error("Please enter the correct user name"))},t=function(e,t,s){t.length<6?s(new Error("The password can not be less than 6 digits")):s()};return{loginForm:{username:"",password:""},loginRules:{username:[{required:!0,trigger:"blur",validator:e}],password:[{required:!0,trigger:"blur",validator:t}]},loading:!1,passwordType:"password",redirect:void 0}},watch:{$route:{handler:function(e){this.redirect=e.query&&e.query.redirect},immediate:!0}},methods:{showPwd:function(){var e=this;"password"===this.passwordType?this.passwordType="":this.passwordType="password",this.$nextTick(function(){e.$refs.password.focus()})},handleLogin:function(){var e=this;this.$refs.loginForm.validate(function(t){if(!t)return console.log("error submit!!"),!1;e.loading=!0,e.$store.dispatch("user/login",e.loginForm).then(function(){e.$router.push({path:e.redirect||"/"}),e.loading=!1}).catch(function(){e.loading=!1})})}}},i=a,l=(s("2017"),s("ec2d"),s("2877")),c=Object(l["a"])(i,n,o,!1,null,"31c14ebf",null);t["default"]=c.exports},ec2d:function(e,t,s){"use strict";var n=s("562b"),o=s.n(n);o.a}}]);

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-101fc062"],{2017:function(e,t,s){"use strict";var n=s("3b76"),o=s.n(n);o.a},"3b76":function(e,t,s){},"562b":function(e,t,s){},"9ed6":function(e,t,s){"use strict";s.r(t);var n=function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("div",{staticClass:"login-container"},[s("el-form",{ref:"loginForm",staticClass:"login-form",attrs:{model:e.loginForm,rules:e.loginRules,"auto-complete":"on","label-position":"left"}},[s("div",{staticClass:"title-container"},[s("h3",{staticClass:"title"},[e._v("Canal Admin Login")])]),e._v(" "),s("el-form-item",{attrs:{prop:"username"}},[s("span",{staticClass:"svg-container"},[s("svg-icon",{attrs:{"icon-class":"user"}})],1),e._v(" "),s("el-input",{ref:"username",attrs:{placeholder:"Username",name:"username",type:"text",tabindex:"1","auto-complete":"on"},model:{value:e.loginForm.username,callback:function(t){e.$set(e.loginForm,"username",t)},expression:"loginForm.username"}})],1),e._v(" "),s("el-form-item",{attrs:{prop:"password"}},[s("span",{staticClass:"svg-container"},[s("svg-icon",{attrs:{"icon-class":"password"}})],1),e._v(" "),s("el-input",{key:e.passwordType,ref:"password",attrs:{type:e.passwordType,placeholder:"Password",name:"password",tabindex:"2","auto-complete":"on"},nativeOn:{keyup:function(t){return!t.type.indexOf("key")&&e._k(t.keyCode,"enter",13,t.key,"Enter")?null:e.handleLogin(t)}},model:{value:e.loginForm.password,callback:function(t){e.$set(e.loginForm,"password",t)},expression:"loginForm.password"}}),e._v(" "),s("span",{staticClass:"show-pwd",on:{click:e.showPwd}},[s("svg-icon",{attrs:{"icon-class":"password"===e.passwordType?"eye":"eye-open"}})],1)],1),e._v(" "),s("el-button",{staticStyle:{width:"100%","margin-bottom":"30px"},attrs:{loading:e.loading,type:"primary"},nativeOn:{click:function(t){return t.preventDefault(),e.handleLogin(t)}}},[e._v("Login")]),e._v(" "),s("div",{staticClass:"tips"})],1)],1)},o=[],r=s("61f7"),a={name:"Login",data:function(){var e=function(e,t,s){Object(r["b"])(t)?s():s(new Error("Please enter the correct user name"))},t=function(e,t,s){t.length<6?s(new Error("The password can not be less than 6 digits")):s()};return{loginForm:{username:"",password:""},loginRules:{username:[{required:!0,trigger:"blur",validator:e}],password:[{required:!0,trigger:"blur",validator:t}]},loading:!1,passwordType:"password",redirect:void 0}},watch:{$route:{handler:function(e){this.redirect=e.query&&e.query.redirect},immediate:!0}},methods:{showPwd:function(){var e=this;"password"===this.passwordType?this.passwordType="":this.passwordType="password",this.$nextTick(function(){e.$refs.password.focus()})},handleLogin:function(){var e=this;this.$refs.loginForm.validate(function(t){if(!t)return console.log("error submit!!"),!1;e.loading=!0,e.$store.dispatch("user/login",e.loginForm).then(function(){e.$router.push({path:e.redirect||"/"}),e.loading=!1}).catch(function(){e.loading=!1})})}}},i=a,l=(s("2017"),s("ec2d"),s("2877")),c=Object(l["a"])(i,n,o,!1,null,"31c14ebf",null);t["default"]=c.exports},ec2d:function(e,t,s){"use strict";var n=s("562b"),o=s.n(n);o.a}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2301924a"],{"10df":function(t,e,n){},1248:function(t,e,n){"use strict";n.r(e);var r=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",[n("el-form",{ref:"form",attrs:{model:t.form}},[n("div",{staticClass:"filter-container",staticStyle:{"padding-left":"10px","padding-top":"20px"}},[n("el-input",{staticClass:"filter-item",staticStyle:{width:"200px"},attrs:{placeholder:"Instance名称"},model:{value:t.form.name,callback:function(e){t.$set(t.form,"name",e)},expression:"form.name"}}),t._v(" "),n("el-select",{staticClass:"filter-item",attrs:{placeholder:"所属集群/主机"},model:{value:t.form.clusterServerId,callback:function(e){t.$set(t.form,"clusterServerId",e)},expression:"form.clusterServerId"}},t._l(t.options,function(e){return n("el-option-group",{key:e.label,attrs:{label:e.label}},t._l(e.options,function(t){return n("el-option",{key:t.value,attrs:{label:t.label,value:t.value}})}),1)}),1),t._v(" "),n("el-button",{staticClass:"filter-item",attrs:{type:"primary"},on:{click:t.onSubmit}},[t._v("保存")]),t._v(" "),n("el-button",{staticClass:"filter-item",attrs:{type:"info"},on:{click:t.onBack}},[t._v("返回")])],1),t._v(" "),n("editor",{attrs:{lang:"properties",theme:"chrome",width:"100%",height:800},on:{init:t.editorInit},model:{value:t.form.content,callback:function(e){t.$set(t.form,"content",e)},expression:"form.content"}})],1)],1)},a=[],c=(n("7f7f"),n("f546")),o=n("d631"),u={components:{editor:n("7c9e")},data:function(){return{options:[],form:{name:"",content:"",clusterServerId:""}}},created:function(){var t=this;Object(o["d"])().then(function(e){t.options=e.data})},methods:{editorInit:function(){n("2099"),n("be9d"),n("2968"),n("e0e5"),n("bb36"),n("0329"),n("95b8"),n("6a21")},onSubmit:function(){var t=this;""!==this.form.name?""!==this.form.clusterServerId?null!==this.form.content&&""!==this.form.content?this.$confirm("确定新建","确定新建",{confirmButtonText:"确定",cancelButtonText:"取消",type:"warning"}).then(function(){Object(c["a"])(t.form).then(function(e){"success"===e.data?(t.$message({message:"新建成功",type:"success"}),t.$router.push("/canalServer/canalInstances")):t.$message({message:"新建失败",type:"error"})})}):this.$message({message:"请输入配置内容",type:"error"}):this.$message({message:"请选择所属集群/主机",type:"error"}):this.$message({message:"请输入Instance名称",type:"error"})},onBack:function(){history.go(-1)}}},i=u,s=(n("2eb8"),n("2877")),l=Object(s["a"])(i,r,a,!1,null,"28f0cd0f",null);e["default"]=l.exports},"2eb8":function(t,e,n){"use strict";var r=n("10df"),a=n.n(r);a.a},d631:function(t,e,n){"use strict";n.d(e,"c",function(){return a}),n.d(e,"a",function(){return c}),n.d(e,"e",function(){return o}),n.d(e,"b",function(){return u}),n.d(e,"d",function(){return i});var r=n("b775");function a(t){return Object(r["a"])({url:"/canal/clusters",method:"get",params:t})}function c(t){return Object(r["a"])({url:"/canal/cluster",method:"post",data:t})}function o(t){return Object(r["a"])({url:"/canal/cluster",method:"put",data:t})}function u(t){return Object(r["a"])({url:"/canal/cluster/"+t,method:"delete"})}function i(){return Object(r["a"])({url:"/canal/clustersAndServers",method:"get"})}},f546:function(t,e,n){"use strict";n.d(e,"e",function(){return a}),n.d(e,"b",function(){return c}),n.d(e,"j",function(){return o}),n.d(e,"a",function(){return u}),n.d(e,"c",function(){return i}),n.d(e,"h",function(){return s}),n.d(e,"i",function(){return l}),n.d(e,"f",function(){return f}),n.d(e,"g",function(){return d}),n.d(e,"d",function(){return m});var r=n("b775");function a(t){return Object(r["a"])({url:"/canal/instances",method:"get",params:t})}function c(t){return Object(r["a"])({url:"/canal/instance/"+t,method:"get"})}function o(t){return Object(r["a"])({url:"/canal/instance",method:"put",data:t})}function u(t){return Object(r["a"])({url:"/canal/instance",method:"post",data:t})}function i(t){return Object(r["a"])({url:"/canal/instance/"+t,method:"delete"})}function s(t,e){return Object(r["a"])({url:"/canal/instance/start/"+t+"/"+e,method:"put"})}function l(t,e){return Object(r["a"])({url:"/canal/instance/stop/"+t+"/"+e,method:"put"})}function f(t,e){return Object(r["a"])({url:"/canal/instance/log/"+t+"/"+e,method:"get"})}function d(t,e){return Object(r["a"])({url:"/canal/instance/status/"+t+"?option="+e,method:"put"})}function m(t){return Object(r["a"])({url:"/canal/active/instances/"+t,method:"get"})}}}]);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-37c49cbf"],{3671:function(t,e,n){"use strict";var a=n("afe4"),c=n.n(a);c.a},9406:function(t,e,n){"use strict";n.r(e);var a=function(){var t=this,e=t.$createElement;t._self._c;return t._m(0)},c=[function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"dashboard-container"},[n("div",{staticClass:"dashboard-text"},[t._v(" ")])])}],s=n("db72"),r=n("2f62"),u={name:"Dashboard",computed:Object(s["a"])({},Object(r["b"])(["name"])),mounted:function(){this.$router.push("/canalServer")}},i=u,o=(n("3671"),n("2877")),d=Object(o["a"])(i,a,c,!1,null,"42037c2b",null);e["default"]=d.exports},afe4:function(t,e,n){}}]);

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-37c49cbf"],{3671:function(t,e,n){"use strict";var a=n("afe4"),c=n.n(a);c.a},9406:function(t,e,n){"use strict";n.r(e);var a=function(){var t=this,e=t.$createElement;t._self._c;return t._m(0)},c=[function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"dashboard-container"},[n("div",{staticClass:"dashboard-text"},[t._v(" ")])])}],s=n("db72"),r=n("2f62"),u={name:"Dashboard",computed:Object(s["a"])({},Object(r["b"])(["name"])),mounted:function(){this.$router.push("/canalServer")}},i=u,o=(n("3671"),n("2877")),d=Object(o["a"])(i,a,c,!1,null,"42037c2b",null);e["default"]=d.exports},afe4:function(t,e,n){}}]);

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-49959c8b"],{"26fc":function(t,s,a){t.exports=a.p+"static/img/404_cloud.0f4bc32b.png"},"8cdb":function(t,s,a){"use strict";a.r(s);var e=function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("div",{staticClass:"wscn-http404-container"},[a("div",{staticClass:"wscn-http404"},[t._m(0),t._v(" "),a("div",{staticClass:"bullshit"},[a("div",{staticClass:"bullshit__oops"},[t._v("OOPS!")]),t._v(" "),t._m(1),t._v(" "),a("div",{staticClass:"bullshit__headline"},[t._v(t._s(t.message))]),t._v(" "),a("div",{staticClass:"bullshit__info"},[t._v("Please check that the URL you entered is correct, or click the button below to return to the homepage.")]),t._v(" "),a("a",{staticClass:"bullshit__return-home",attrs:{href:""}},[t._v("Back to home")])])])])},c=[function(){var t=this,s=t.$createElement,e=t._self._c||s;return e("div",{staticClass:"pic-404"},[e("img",{staticClass:"pic-404__parent",attrs:{src:a("a36b"),alt:"404"}}),t._v(" "),e("img",{staticClass:"pic-404__child left",attrs:{src:a("26fc"),alt:"404"}}),t._v(" "),e("img",{staticClass:"pic-404__child mid",attrs:{src:a("26fc"),alt:"404"}}),t._v(" "),e("img",{staticClass:"pic-404__child right",attrs:{src:a("26fc"),alt:"404"}})])},function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("div",{staticClass:"bullshit__info"},[t._v("All rights reserved\n "),a("a",{staticStyle:{color:"#20a0ff"},attrs:{href:"https://wallstreetcn.com",target:"_blank"}},[t._v("wallstreetcn")])])}],i={name:"Page404",computed:{message:function(){return"The webmaster said that you can not enter this page..."}}},l=i,n=(a("97ef"),a("2877")),r=Object(n["a"])(l,e,c,!1,null,"c095f994",null);s["default"]=r.exports},"97ef":function(t,s,a){"use strict";var e=a("b51e"),c=a.n(e);c.a},a36b:function(t,s,a){t.exports=a.p+"static/img/404.a57b6f31.png"},b51e:function(t,s,a){}}]);

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-49959c8b"],{"26fc":function(t,s,a){t.exports=a.p+"static/img/404_cloud.0f4bc32b.png"},"8cdb":function(t,s,a){"use strict";a.r(s);var e=function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("div",{staticClass:"wscn-http404-container"},[a("div",{staticClass:"wscn-http404"},[t._m(0),t._v(" "),a("div",{staticClass:"bullshit"},[a("div",{staticClass:"bullshit__oops"},[t._v("OOPS!")]),t._v(" "),t._m(1),t._v(" "),a("div",{staticClass:"bullshit__headline"},[t._v(t._s(t.message))]),t._v(" "),a("div",{staticClass:"bullshit__info"},[t._v("Please check that the URL you entered is correct, or click the button below to return to the homepage.")]),t._v(" "),a("a",{staticClass:"bullshit__return-home",attrs:{href:""}},[t._v("Back to home")])])])])},c=[function(){var t=this,s=t.$createElement,e=t._self._c||s;return e("div",{staticClass:"pic-404"},[e("img",{staticClass:"pic-404__parent",attrs:{src:a("a36b"),alt:"404"}}),t._v(" "),e("img",{staticClass:"pic-404__child left",attrs:{src:a("26fc"),alt:"404"}}),t._v(" "),e("img",{staticClass:"pic-404__child mid",attrs:{src:a("26fc"),alt:"404"}}),t._v(" "),e("img",{staticClass:"pic-404__child right",attrs:{src:a("26fc"),alt:"404"}})])},function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("div",{staticClass:"bullshit__info"},[t._v("All rights reserved\n "),a("a",{staticStyle:{color:"#20a0ff"},attrs:{href:"https://wallstreetcn.com",target:"_blank"}},[t._v("wallstreetcn")])])}],i={name:"Page404",computed:{message:function(){return"The webmaster said that you can not enter this page..."}}},l=i,n=(a("97ef"),a("2877")),r=Object(n["a"])(l,e,c,!1,null,"c095f994",null);s["default"]=r.exports},"97ef":function(t,s,a){"use strict";var e=a("b51e"),c=a.n(e);c.a},a36b:function(t,s,a){t.exports=a.p+"static/img/404.a57b6f31.png"},b51e:function(t,s,a){}}]);

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-4f09fed2"],{"68d6":function(t,e,n){"use strict";var r=n("9d07"),o=n.n(r);o.a},"9d07":function(t,e,n){},c6ed:function(t,e,n){"use strict";n.d(e,"c",function(){return o}),n.d(e,"a",function(){return c}),n.d(e,"g",function(){return u}),n.d(e,"b",function(){return a}),n.d(e,"e",function(){return d}),n.d(e,"f",function(){return i}),n.d(e,"d",function(){return f});var r=n("b775");function o(t){return Object(r["a"])({url:"/nodeServers",method:"get",params:t})}function c(t){return Object(r["a"])({url:"/nodeServer",method:"post",data:t})}function u(t){return Object(r["a"])({url:"/nodeServer",method:"put",data:t})}function a(t){return Object(r["a"])({url:"/nodeServer/"+t,method:"delete"})}function d(t){return Object(r["a"])({url:"/nodeServer/start/"+t,method:"put"})}function i(t){return Object(r["a"])({url:"/nodeServer/stop/"+t,method:"put"})}function f(t){return Object(r["a"])({url:"/nodeServer/log/"+t,method:"get"})}},caf8:function(t,e,n){"use strict";n.r(e);var r=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",[n("el-form",{ref:"form",attrs:{model:t.form}},[n("div",{staticStyle:{"padding-left":"10px","padding-right":"10px","padding-top":"20px"}},[n("el-form-item",[t._v("\n canal.log    \n "),n("el-button",{attrs:{type:"primary"},on:{click:t.onRefresh}},[t._v("刷新")]),t._v(" "),n("el-button",{attrs:{type:"info"},on:{click:t.onBack}},[t._v("返回")])],1),t._v(" "),n("el-input",{attrs:{rows:35,readonly:"readonly",type:"textarea"},model:{value:t.form.desc,callback:function(e){t.$set(t.form,"desc",e)},expression:"form.desc"}})],1)])],1)},o=[],c=n("c6ed"),u={data:function(){return{form:{desc:""}}},created:function(){this.fetchData()},methods:{fetchData:function(){var t=this;Object(c["d"])(this.$route.query.id).then(function(e){t.form.desc=e.data})},onRefresh:function(){this.fetchData()},onBack:function(){history.go(-1)}}},a=u,d=(n("68d6"),n("2877")),i=Object(d["a"])(a,r,o,!1,null,"64c3fae5",null);e["default"]=i.exports}}]);

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-4f09fed2"],{"68d6":function(t,e,n){"use strict";var r=n("9d07"),o=n.n(r);o.a},"9d07":function(t,e,n){},c6ed:function(t,e,n){"use strict";n.d(e,"c",function(){return o}),n.d(e,"a",function(){return c}),n.d(e,"g",function(){return u}),n.d(e,"b",function(){return a}),n.d(e,"e",function(){return d}),n.d(e,"f",function(){return i}),n.d(e,"d",function(){return f});var r=n("b775");function o(t){return Object(r["a"])({url:"/nodeServers",method:"get",params:t})}function c(t){return Object(r["a"])({url:"/nodeServer",method:"post",data:t})}function u(t){return Object(r["a"])({url:"/nodeServer",method:"put",data:t})}function a(t){return Object(r["a"])({url:"/nodeServer/"+t,method:"delete"})}function d(t){return Object(r["a"])({url:"/nodeServer/start/"+t,method:"put"})}function i(t){return Object(r["a"])({url:"/nodeServer/stop/"+t,method:"put"})}function f(t){return Object(r["a"])({url:"/nodeServer/log/"+t,method:"get"})}},caf8:function(t,e,n){"use strict";n.r(e);var r=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",[n("el-form",{ref:"form",attrs:{model:t.form}},[n("div",{staticStyle:{"padding-left":"10px","padding-right":"10px","padding-top":"20px"}},[n("el-form-item",[t._v("\n canal.log    \n "),n("el-button",{attrs:{type:"primary"},on:{click:t.onRefresh}},[t._v("刷新")]),t._v(" "),n("el-button",{attrs:{type:"info"},on:{click:t.onBack}},[t._v("返回")])],1),t._v(" "),n("el-input",{attrs:{rows:35,readonly:"readonly",type:"textarea"},model:{value:t.form.desc,callback:function(e){t.$set(t.form,"desc",e)},expression:"form.desc"}})],1)])],1)},o=[],c=n("c6ed"),u={data:function(){return{form:{desc:""}}},created:function(){this.fetchData()},methods:{fetchData:function(){var t=this;Object(c["d"])(this.$route.query.id).then(function(e){t.form.desc=e.data})},onRefresh:function(){this.fetchData()},onBack:function(){history.go(-1)}}},a=u,d=(n("68d6"),n("2877")),i=Object(d["a"])(a,r,o,!1,null,"64c3fae5",null);e["default"]=i.exports}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-5afa45f5"],{"0984":function(t,e,n){},"0f47":function(t,e,n){"use strict";var o=n("0984"),r=n.n(o);r.a},aa99:function(t,e,n){"use strict";n.r(e);var o=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",[n("el-form",{ref:"form",attrs:{model:t.form}},[n("div",{staticStyle:{"padding-left":"10px","padding-top":"20px"}},[n("el-form-item",[t._v("\n "+t._s(t.form.name)+"    \n "),n("el-button",{attrs:{type:"primary"},on:{click:t.onSubmit}},[t._v("保存")]),t._v(" "),n("el-button",{attrs:{type:"warning"},on:{click:t.onCancel}},[t._v("重置")]),t._v(" "),n("el-button",{attrs:{type:"success"},on:{click:t.onLoadTemplate}},[t._v("载入模板")]),t._v(" "),n("el-button",{attrs:{type:"info"},on:{click:t.onBack}},[t._v("返回")])],1)],1),t._v(" "),n("editor",{attrs:{lang:"properties",theme:"chrome",width:"100%",height:800},on:{init:t.editorInit},model:{value:t.form.content,callback:function(e){t.$set(t.form,"content",e)},expression:"form.content"}})],1)],1)},r=[],a=(n("7f7f"),n("b775"));function c(t,e){return Object(a["a"])({url:"/canal/config/"+t+"/"+e,method:"get"})}function i(t){return Object(a["a"])({url:"/canal/config",method:"put",data:t})}function s(){return Object(a["a"])({url:"/canal/config/template",method:"get"})}var u={components:{editor:n("7c9e")},data:function(){return{form:{id:null,name:"",content:"",serverId:null,clusterId:null}}},created:function(){this.loadCanalConfig()},methods:{editorInit:function(){n("2099"),n("be9d"),n("2968"),n("e0e5"),n("bb36"),n("0329"),n("95b8"),n("6a21")},loadCanalConfig:function(){var t=this,e=0,n=0;this.$route.query.clusterId?e=this.$route.query.clusterId:this.$route.query.serverId&&(n=this.$route.query.serverId),c(e,n).then(function(e){var n=e.data;t.form.id=n.id,t.form.name=n.name,t.form.content=n.content,t.form.serverId=t.$route.query.serverId,t.form.clusterId=t.$route.query.clusterId})},onSubmit:function(){var t=this;null!==this.form.content&&""!==this.form.content?this.$confirm("修改主配置可能会导致Server重启,是否继续?","确定修改",{confirmButtonText:"确定",cancelButtonText:"取消",type:"warning"}).then(function(){i(t.form).then(function(e){"success"===e.data?(t.$message({message:"保存成功",type:"success"}),t.loadCanalConfig()):t.$message({message:"保存失败",type:"error"})})}):this.$message({message:"配置内容不能为空",type:"error"})},onCancel:function(){this.loadCanalConfig()},onBack:function(){history.go(-1)},onLoadTemplate:function(){var t=this;s().then(function(e){t.form.content=e.data})}}},f=u,l=(n("0f47"),n("2877")),m=Object(l["a"])(f,o,r,!1,null,"2e6a6604",null);e["default"]=m.exports}}]);

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-5b373aad"],{1248:function(t,e,n){"use strict";n.r(e);var r=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",[n("el-form",{ref:"form",attrs:{model:t.form}},[n("div",{staticClass:"filter-container",staticStyle:{"padding-left":"10px","padding-top":"20px"}},[n("el-input",{staticClass:"filter-item",staticStyle:{width:"200px"},attrs:{placeholder:"Instance名称"},model:{value:t.form.name,callback:function(e){t.$set(t.form,"name",e)},expression:"form.name"}}),t._v(" "),n("el-select",{staticClass:"filter-item",attrs:{placeholder:"所属集群/主机"},model:{value:t.form.clusterServerId,callback:function(e){t.$set(t.form,"clusterServerId",e)},expression:"form.clusterServerId"}},t._l(t.options,function(e){return n("el-option-group",{key:e.label,attrs:{label:e.label}},t._l(e.options,function(t){return n("el-option",{key:t.value,attrs:{label:t.label,value:t.value}})}),1)}),1),t._v(" "),n("el-button",{staticClass:"filter-item",attrs:{type:"primary"},on:{click:t.onSubmit}},[t._v("保存")]),t._v(" "),n("el-button",{staticClass:"filter-item",attrs:{type:"success"},on:{click:t.onLoadTemplate}},[t._v("载入模板")]),t._v(" "),n("el-button",{staticClass:"filter-item",attrs:{type:"info"},on:{click:t.onBack}},[t._v("返回")])],1),t._v(" "),n("editor",{attrs:{lang:"properties",theme:"chrome",width:"100%",height:800},on:{init:t.editorInit},model:{value:t.form.content,callback:function(e){t.$set(t.form,"content",e)},expression:"form.content"}})],1)],1)},a=[],c=(n("7f7f"),n("f546")),o=n("d631"),u={components:{editor:n("7c9e")},data:function(){return{options:[],form:{name:"",content:"",clusterServerId:""}}},created:function(){var t=this;Object(o["d"])().then(function(e){t.options=e.data})},methods:{editorInit:function(){n("2099"),n("be9d"),n("2968"),n("e0e5"),n("bb36"),n("0329"),n("95b8"),n("6a21")},onSubmit:function(){var t=this;""!==this.form.name?""!==this.form.clusterServerId?null!==this.form.content&&""!==this.form.content?this.$confirm("确定新建","确定新建",{confirmButtonText:"确定",cancelButtonText:"取消",type:"warning"}).then(function(){Object(c["a"])(t.form).then(function(e){"success"===e.data?(t.$message({message:"新建成功",type:"success"}),t.$router.push("/canalServer/canalInstances")):t.$message({message:"新建失败",type:"error"})})}):this.$message({message:"请输入配置内容",type:"error"}):this.$message({message:"请选择所属集群/主机",type:"error"}):this.$message({message:"请输入Instance名称",type:"error"})},onBack:function(){history.go(-1)},onLoadTemplate:function(){var t=this;Object(c["f"])().then(function(e){t.form.content=e.data})}}},i=u,s=(n("890e"),n("2877")),l=Object(s["a"])(i,r,a,!1,null,"e884438a",null);e["default"]=l.exports},"41be":function(t,e,n){},"890e":function(t,e,n){"use strict";var r=n("41be"),a=n.n(r);a.a},d631:function(t,e,n){"use strict";n.d(e,"c",function(){return a}),n.d(e,"a",function(){return c}),n.d(e,"e",function(){return o}),n.d(e,"b",function(){return u}),n.d(e,"d",function(){return i});var r=n("b775");function a(t){return Object(r["a"])({url:"/canal/clusters",method:"get",params:t})}function c(t){return Object(r["a"])({url:"/canal/cluster",method:"post",data:t})}function o(t){return Object(r["a"])({url:"/canal/cluster",method:"put",data:t})}function u(t){return Object(r["a"])({url:"/canal/cluster/"+t,method:"delete"})}function i(){return Object(r["a"])({url:"/canal/clustersAndServers",method:"get"})}},f546:function(t,e,n){"use strict";n.d(e,"e",function(){return a}),n.d(e,"b",function(){return c}),n.d(e,"k",function(){return o}),n.d(e,"a",function(){return u}),n.d(e,"c",function(){return i}),n.d(e,"i",function(){return s}),n.d(e,"j",function(){return l}),n.d(e,"g",function(){return f}),n.d(e,"h",function(){return d}),n.d(e,"d",function(){return m}),n.d(e,"f",function(){return p});var r=n("b775");function a(t){return Object(r["a"])({url:"/canal/instances",method:"get",params:t})}function c(t){return Object(r["a"])({url:"/canal/instance/"+t,method:"get"})}function o(t){return Object(r["a"])({url:"/canal/instance",method:"put",data:t})}function u(t){return Object(r["a"])({url:"/canal/instance",method:"post",data:t})}function i(t){return Object(r["a"])({url:"/canal/instance/"+t,method:"delete"})}function s(t,e){return Object(r["a"])({url:"/canal/instance/start/"+t+"/"+e,method:"put"})}function l(t,e){return Object(r["a"])({url:"/canal/instance/stop/"+t+"/"+e,method:"put"})}function f(t,e){return Object(r["a"])({url:"/canal/instance/log/"+t+"/"+e,method:"get"})}function d(t,e){return Object(r["a"])({url:"/canal/instance/status/"+t+"?option="+e,method:"put"})}function m(t){return Object(r["a"])({url:"/canal/active/instances/"+t,method:"get"})}function p(){return Object(r["a"])({url:"/canal/instance/template",method:"get"})}}}]);

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-69386cf0"],{2829:function(e,t,s){},"646d":function(e,t,s){"use strict";var r=s("2829"),o=s.n(r);o.a},"9fb7":function(e,t,s){"use strict";s.r(t);var r=function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("div",{staticClass:"app-container",staticStyle:{width:"600px"}},[s("el-form",{ref:"form",attrs:{rules:e.rules,model:e.form,"label-width":"120px"}},[s("el-form-item",{attrs:{label:"用户名",prop:"username"}},[s("el-input",{staticStyle:{width:"200px"},model:{value:e.form.username,callback:function(t){e.$set(e.form,"username",t)},expression:"form.username"}})],1),e._v(" "),s("el-form-item",{attrs:{label:"旧密码",prop:"oldPassword"}},[s("el-input",{staticStyle:{width:"200px"},attrs:{type:"password"},model:{value:e.form.oldPassword,callback:function(t){e.$set(e.form,"oldPassword",t)},expression:"form.oldPassword"}})],1),e._v(" "),s("el-form-item",{attrs:{label:"密码",prop:"password"}},[s("el-input",{staticStyle:{width:"200px"},attrs:{placeholder:"空为不修改密码",type:"password"},model:{value:e.form.password,callback:function(t){e.$set(e.form,"password",t)},expression:"form.password"}})],1),e._v(" "),s("el-form-item",[s("el-button",{attrs:{type:"primary"},on:{click:e.onSubmit}},[e._v("修改")]),e._v(" "),s("el-button",{on:{click:e.onCancel}},[e._v("取消")])],1)],1)],1)},o=[],a=s("c24f"),n=s("5f87"),l={data:function(){return{form:{username:"",oldPassword:"",password:null},rules:{username:[{required:!0,message:"用户名能为空",trigger:"change"}],oldPassword:[{required:!0,message:"旧密码不能为空",trigger:"change"}]}}},created:function(){this.fetchUserInfo()},methods:{fetchUserInfo:function(){var e=this;Object(a["a"])(Object(n["a"])()).then(function(t){e.form.username=t.data.username})},onSubmit:function(){var e=this;this.$refs["form"].validate(function(t){t&&Object(a["d"])(e.form).then(function(t){"success"===t.data?(e.form.oldPassword="",e.form.password=null,e.$nextTick(function(){e.$refs["form"].clearValidate()}),e.$message({message:"修改用户信息成功",type:"success"})):e.$message({message:"修改用户信息成功",type:"error"})})})},onCancel:function(){history.go(-1)}}},i=l,c=(s("646d"),s("2877")),f=Object(c["a"])(i,r,o,!1,null,"756ebb70",null);t["default"]=f.exports}}]);

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-69386cf0"],{2829:function(e,t,s){},"646d":function(e,t,s){"use strict";var r=s("2829"),o=s.n(r);o.a},"9fb7":function(e,t,s){"use strict";s.r(t);var r=function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("div",{staticClass:"app-container",staticStyle:{width:"600px"}},[s("el-form",{ref:"form",attrs:{rules:e.rules,model:e.form,"label-width":"120px"}},[s("el-form-item",{attrs:{label:"用户名",prop:"username"}},[s("el-input",{staticStyle:{width:"200px"},model:{value:e.form.username,callback:function(t){e.$set(e.form,"username",t)},expression:"form.username"}})],1),e._v(" "),s("el-form-item",{attrs:{label:"旧密码",prop:"oldPassword"}},[s("el-input",{staticStyle:{width:"200px"},attrs:{type:"password"},model:{value:e.form.oldPassword,callback:function(t){e.$set(e.form,"oldPassword",t)},expression:"form.oldPassword"}})],1),e._v(" "),s("el-form-item",{attrs:{label:"密码",prop:"password"}},[s("el-input",{staticStyle:{width:"200px"},attrs:{placeholder:"空为不修改密码",type:"password"},model:{value:e.form.password,callback:function(t){e.$set(e.form,"password",t)},expression:"form.password"}})],1),e._v(" "),s("el-form-item",[s("el-button",{attrs:{type:"primary"},on:{click:e.onSubmit}},[e._v("修改")]),e._v(" "),s("el-button",{on:{click:e.onCancel}},[e._v("取消")])],1)],1)],1)},o=[],a=s("c24f"),n=s("5f87"),l={data:function(){return{form:{username:"",oldPassword:"",password:null},rules:{username:[{required:!0,message:"用户名能为空",trigger:"change"}],oldPassword:[{required:!0,message:"旧密码不能为空",trigger:"change"}]}}},created:function(){this.fetchUserInfo()},methods:{fetchUserInfo:function(){var e=this;Object(a["a"])(Object(n["a"])()).then(function(t){e.form.username=t.data.username})},onSubmit:function(){var e=this;this.$refs["form"].validate(function(t){t&&Object(a["d"])(e.form).then(function(t){"success"===t.data?(e.form.oldPassword="",e.form.password=null,e.$nextTick(function(){e.$refs["form"].clearValidate()}),e.$message({message:"修改用户信息成功",type:"success"})):e.$message({message:"修改用户信息成功",type:"error"})})})},onCancel:function(){history.go(-1)}}},i=l,c=(s("646d"),s("2877")),f=Object(c["a"])(i,r,o,!1,null,"756ebb70",null);t["default"]=f.exports}}]);

View File

@@ -0,0 +1 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-7ec889b7"],{"11ec":function(t,n,e){},"7f19":function(t,n,e){"use strict";var c=e("11ec"),r=e.n(c);r.a},"7f84":function(t,n,e){"use strict";e.r(n);var c=function(){var t=this,n=t.$createElement,e=t._self._c||n;return e("div",[e("el-form",{ref:"form",attrs:{model:t.form}},[e("div",{staticStyle:{"padding-left":"10px","padding-right":"10px","padding-top":"20px"}},[e("el-form-item",[t._v("\n "+t._s(t.form.instance)+"    \n "),e("el-button",{attrs:{type:"primary"},on:{click:t.onRefresh}},[t._v("刷新")]),t._v(" "),e("el-button",{attrs:{type:"info"},on:{click:t.onBack}},[t._v("返回")])],1),t._v(" "),e("el-input",{attrs:{rows:35,readonly:"readonly",type:"textarea"},model:{value:t.form.desc,callback:function(n){t.$set(t.form,"desc",n)},expression:"form.desc"}})],1)])],1)},r=[],a=e("f546"),o={data:function(){return{form:{instance:"",desc:""}}},created:function(){this.fetchData()},methods:{fetchData:function(){var t=this;Object(a["f"])(this.$route.query.id,this.$route.query.nodeId).then(function(n){t.form.instance=n.data.instance+".log",t.form.desc=n.data.log})},onRefresh:function(){this.fetchData()},onBack:function(){history.go(-1)}}},u=o,i=(e("7f19"),e("2877")),f=Object(i["a"])(u,c,r,!1,null,"0b80198c",null);n["default"]=f.exports},f546:function(t,n,e){"use strict";e.d(n,"e",function(){return r}),e.d(n,"b",function(){return a}),e.d(n,"j",function(){return o}),e.d(n,"a",function(){return u}),e.d(n,"c",function(){return i}),e.d(n,"h",function(){return f}),e.d(n,"i",function(){return s}),e.d(n,"f",function(){return d}),e.d(n,"g",function(){return l}),e.d(n,"d",function(){return h});var c=e("b775");function r(t){return Object(c["a"])({url:"/canal/instances",method:"get",params:t})}function a(t){return Object(c["a"])({url:"/canal/instance/"+t,method:"get"})}function o(t){return Object(c["a"])({url:"/canal/instance",method:"put",data:t})}function u(t){return Object(c["a"])({url:"/canal/instance",method:"post",data:t})}function i(t){return Object(c["a"])({url:"/canal/instance/"+t,method:"delete"})}function f(t,n){return Object(c["a"])({url:"/canal/instance/start/"+t+"/"+n,method:"put"})}function s(t,n){return Object(c["a"])({url:"/canal/instance/stop/"+t+"/"+n,method:"put"})}function d(t,n){return Object(c["a"])({url:"/canal/instance/log/"+t+"/"+n,method:"get"})}function l(t,n){return Object(c["a"])({url:"/canal/instance/status/"+t+"?option="+n,method:"put"})}function h(t){return Object(c["a"])({url:"/canal/active/instances/"+t,method:"get"})}}}]);

Some files were not shown because too many files have changed in this diff Show More