Compare commits
880
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d53bfd7ee7 | ||
|
|
ccc45b672c | ||
|
|
0d42bdafe2 | ||
|
|
95050ff382 | ||
|
|
5addd246ac | ||
|
|
cfc95e2b7a | ||
|
|
44187d9e3a | ||
|
|
c76e8ba0e4 | ||
|
|
1bcae9dc49 | ||
|
|
3a388308d9 | ||
|
|
a60ec6fa42 | ||
|
|
456945fe3a | ||
|
|
ba24c15833 | ||
|
|
4e0b8a0278 | ||
|
|
274e91f8aa | ||
|
|
89055f7405 | ||
|
|
e8dba41216 | ||
|
|
f52b2cccd5 | ||
|
|
7386327af9 | ||
|
|
c48407b449 | ||
|
|
d92badeb45 | ||
|
|
7093179cc8 | ||
|
|
bf89974f7c | ||
|
|
2e1b7a5093 | ||
|
|
3d4dbc9a8c | ||
|
|
e376a129ed | ||
|
|
983ff5fc73 | ||
|
|
3a74f6124a | ||
|
|
bd974e3da8 | ||
|
|
86cd805a64 | ||
|
|
8b54b9e596 | ||
|
|
2d3baeab7d | ||
|
|
7b245c5a64 | ||
|
|
78af6b1130 | ||
|
|
7547cac3c5 | ||
|
|
066743cfd6 | ||
|
|
fdd0763567 | ||
|
|
bcdbf75dc5 | ||
|
|
db23f15b61 | ||
|
|
44722fe01c | ||
|
|
be9813142f | ||
|
|
c48e80a163 | ||
|
|
c1bfad4f2d | ||
|
|
10f4ef6acd | ||
|
|
4c0d82fb98 | ||
|
|
7cec0994e9 | ||
|
|
483044916d | ||
|
|
c8bb6e200c | ||
|
|
9cc6b67426 | ||
|
|
62072ea9ab | ||
|
|
f3daceea6b | ||
|
|
4efc0f6078 | ||
|
|
40d2a8c6cc | ||
|
|
28da536ec8 | ||
|
|
6f129bf524 | ||
|
|
31ae308760 | ||
|
|
c2ce662f9a | ||
|
|
7044e172f5 | ||
|
|
c070757da0 | ||
|
|
92dd2bb189 | ||
|
|
8639b45fe9 | ||
|
|
6b9487a0a0 | ||
|
|
5741f5a347 | ||
|
|
4c3ef6772c | ||
|
|
64824bc5aa | ||
|
|
23209f24ed | ||
|
|
015026ea91 | ||
|
|
c7967e989c | ||
|
|
e4b37f33de | ||
|
|
598f15a404 | ||
|
|
ec5f1eb10d | ||
|
|
28c0aac98d | ||
|
|
50ea6870dc | ||
|
|
317439670c | ||
|
|
64ff140304 | ||
|
|
a93af74539 | ||
|
|
fc39b2af68 | ||
|
|
e87a0c3b63 | ||
|
|
b43043f4e4 | ||
|
|
60a46ae941 | ||
|
|
b5c05c2fd2 | ||
|
|
0cf18976d9 | ||
|
|
803c0607d3 | ||
|
|
f04e742f4f | ||
|
|
c81034dda3 | ||
|
|
a847660a45 | ||
|
|
4957083493 | ||
|
|
24f0eece42 | ||
|
|
adccc0e912 | ||
|
|
8728beb4b6 | ||
|
|
5bea876ec9 | ||
|
|
6c494f8892 | ||
|
|
74489d1bba | ||
|
|
5ac0f7b710 | ||
|
|
75b4911caf | ||
|
|
95602f4784 | ||
|
|
058e1fc546 | ||
|
|
e808c6ec70 | ||
|
|
ce7948d6e7 | ||
|
|
8df6d6fed8 | ||
|
|
f9989e0856 | ||
|
|
4075a0623f | ||
|
|
37fc248ebf | ||
|
|
d0a83aad27 | ||
|
|
3b9ff26d1a | ||
|
|
4f7842c843 | ||
|
|
b870e7ecd0 | ||
|
|
331dd63c5f | ||
|
|
72c4daba5f | ||
|
|
7427a74612 | ||
|
|
24fae6fa52 | ||
|
|
4c690fb5a3 | ||
|
|
8f088cddc0 | ||
|
|
e94ee64316 | ||
|
|
3165cb9640 | ||
|
|
2b738f4e04 | ||
|
|
16f335c381 | ||
|
|
a8bf2b641f | ||
|
|
67faf0e03e | ||
|
|
8cb85753bb | ||
|
|
df86f94409 | ||
|
|
80594444f7 | ||
|
|
d8996432b9 | ||
|
|
69df50fe17 | ||
|
|
c5736bd5f3 | ||
|
|
55030476ee | ||
|
|
cd007088db | ||
|
|
1fc880f255 | ||
|
|
8c68cf833a | ||
|
|
0de8f4691e | ||
|
|
5a2efbf9cb | ||
|
|
dfab98fce1 | ||
|
|
e5c7bbffbf | ||
|
|
e139698f6e | ||
|
|
2b8120b6ed | ||
|
|
68ebb3a957 | ||
|
|
21309cb065 | ||
|
|
386567a633 | ||
|
|
95c69bb854 | ||
|
|
6febcf1a0f | ||
|
|
fadfe1d6ca | ||
|
|
a7b12c7098 | ||
|
|
2c320e8618 | ||
|
|
d76d25df6c | ||
|
|
b95c0d03cf | ||
|
|
08af40221f | ||
|
|
d13c1eb17c | ||
|
|
82ed9db8ae | ||
|
|
d3195ac973 | ||
|
|
15e29d276a | ||
|
|
d715ff64b4 | ||
|
|
f63ce4ffe6 | ||
|
|
a7f9a779ad | ||
|
|
39aeeb8c58 | ||
|
|
9826948ebb | ||
|
|
d6ef66a281 | ||
|
|
97640b717b | ||
|
|
20e7becfe9 | ||
|
|
99b34ff632 | ||
|
|
739963acd0 | ||
|
|
0ec4699149 | ||
|
|
c101ae2280 | ||
|
|
a137deeaff | ||
|
|
c5be5b7a8a | ||
|
|
95bed6bcf6 | ||
|
|
661d119265 | ||
|
|
08167c95c7 | ||
|
|
37c5168a7d | ||
|
|
579fa72ae2 | ||
|
|
844c019433 | ||
|
|
c94e3a0024 | ||
|
|
f6ca489984 | ||
|
|
b2b69307f9 | ||
|
|
531b535228 | ||
|
|
99cccd2113 | ||
|
|
38d8c3d462 | ||
|
|
024ffc2aa2 | ||
|
|
a81d2710ff | ||
|
|
4bad0e88f8 | ||
|
|
0181dfaa42 | ||
|
|
0411d99e7b | ||
|
|
8fcdd7f798 | ||
|
|
b0f4e4ee78 | ||
|
|
d448455ebf | ||
|
|
e07fdb6426 | ||
|
|
38336b249e | ||
|
|
e7b81cd83a | ||
|
|
2810e7e815 | ||
|
|
024986a195 | ||
|
|
9b2ad0eafd | ||
|
|
03d46f150f | ||
|
|
b488ca02d8 | ||
|
|
17a6ba9c9c | ||
|
|
3b618740d4 | ||
|
|
06bfcda275 | ||
|
|
5821c1f606 | ||
|
|
a2360efff9 | ||
|
|
51c469aa6f | ||
|
|
6b3bf31404 | ||
|
|
941ed7f60d | ||
|
|
9db2bd291b | ||
|
|
95b26c4d1d | ||
|
|
b114e0cb15 | ||
|
|
5c96bf282e | ||
|
|
6d280d1048 | ||
|
|
cf4db1625d | ||
|
|
df98fec945 | ||
|
|
e03705e211 | ||
|
|
4df60a0fb5 | ||
|
|
078673c6a9 | ||
|
|
4bd91a1079 | ||
|
|
05ad671cc1 | ||
|
|
43045bc672 | ||
|
|
971c9a88c2 | ||
|
|
7b6e482827 | ||
|
|
ad39559f8c | ||
|
|
2707c12570 | ||
|
|
7dab5ac6c2 | ||
|
|
9d4b61f083 | ||
|
|
a34f9ae97b | ||
|
|
64ca62b986 | ||
|
|
14c1af4c80 | ||
|
|
7436d9f06f | ||
|
|
c4599df1c2 | ||
|
|
7abf50f233 | ||
|
|
1781381187 | ||
|
|
5d4210bd22 | ||
|
|
9888fae559 | ||
|
|
81ab7e9783 | ||
|
|
b891afa50e | ||
|
|
8187d7c5df | ||
|
|
e7f069850f | ||
|
|
d377a92a5d | ||
|
|
f664af1af4 | ||
|
|
5e2eabb3c2 | ||
|
|
f2368e5469 | ||
|
|
fd4c8db92b | ||
|
|
f21abc13af | ||
|
|
ae474d67ad | ||
|
|
0c4711ab62 | ||
|
|
072420c32b | ||
|
|
1473a4a6b5 | ||
|
|
0fff514876 | ||
|
|
4017de5102 | ||
|
|
fcc1fc78ae | ||
|
|
17efc2ada0 | ||
|
|
a68bd28852 | ||
|
|
1de8b27da1 | ||
|
|
01dc8ce2b8 | ||
|
|
f7e490f7a0 | ||
|
|
8cadd585c6 | ||
|
|
57c3226e56 | ||
|
|
79a3961da1 | ||
|
|
62338a945e | ||
|
|
e07db7b178 | ||
|
|
05cc627b5d | ||
|
|
44beaf1916 | ||
|
|
476244b26d | ||
|
|
cfdf509f9a | ||
|
|
c14ed724ff | ||
|
|
5a4cfeab46 | ||
|
|
947cf130ab | ||
|
|
0ef21e4155 | ||
|
|
83b760494d | ||
|
|
aba8df699c | ||
|
|
0c5f5c128f | ||
|
|
ad62275cd7 | ||
|
|
f6d03bcb37 | ||
|
|
5284aa9793 | ||
|
|
10276e254a | ||
|
|
597a08ac37 | ||
|
|
8211ef35e6 | ||
|
|
a20960834b | ||
|
|
7b6b332463 | ||
|
|
43f869599d | ||
|
|
e3d05f6201 | ||
|
|
032301102c | ||
|
|
bb64d84298 | ||
|
|
ccd39e9d62 | ||
|
|
32031da9db | ||
|
|
9a2e890f57 | ||
|
|
8f76b33214 | ||
|
|
a7467539c7 | ||
|
|
db5495d158 | ||
|
|
b04d948064 | ||
|
|
5f8eb4d261 | ||
|
|
197ca67c8b | ||
|
|
851637611f | ||
|
|
4ebf17f57b | ||
|
|
2f214ce3a6 | ||
|
|
8d4fbb395b | ||
|
|
d935c39648 | ||
|
|
6e0e3fbef1 | ||
|
|
cbe88af2e3 | ||
|
|
ba9d9dea6d | ||
|
|
2d88d803ee | ||
|
|
ff1fef1eb6 | ||
|
|
4beb1e6855 | ||
|
|
d2bbe9b061 | ||
|
|
171bedf488 | ||
|
|
e387cfdee5 | ||
|
|
47d7914304 | ||
|
|
e4177ab0ce | ||
|
|
739f3b4fa1 | ||
|
|
60441d8b31 | ||
|
|
ca41be8412 | ||
|
|
18b304f99d | ||
|
|
7f9dce4b77 | ||
|
|
f26adff7c7 | ||
|
|
77646c0185 | ||
|
|
7e4912cd2e | ||
|
|
70a4f0363d | ||
|
|
725960d46e | ||
|
|
b00d347eb0 | ||
|
|
92666b41e8 | ||
|
|
f1753e79f9 | ||
|
|
f1a998b2d5 | ||
|
|
03e6662ce5 | ||
|
|
b845a67666 | ||
|
|
690222bde8 | ||
|
|
63ad80c8c4 | ||
|
|
de501c85a1 | ||
|
|
670b7830a5 | ||
|
|
fdf150bd24 | ||
|
|
c66b6a0f0f | ||
|
|
a91aebafc2 | ||
|
|
35fcd56822 | ||
|
|
a63e982115 | ||
|
|
4bdf3d766f | ||
|
|
779ebad890 | ||
|
|
b348260288 | ||
|
|
3011c3292b | ||
|
|
94b8fdbbf4 | ||
|
|
1dd9934f9c | ||
|
|
3dec7c50ce | ||
|
|
ecd9ababa1 | ||
|
|
b0eaa156cc | ||
|
|
a70178afef | ||
|
|
114c3bd020 | ||
|
|
f9e19d46c4 | ||
|
|
661752096c | ||
|
|
100b43a5db | ||
|
|
5e116de264 | ||
|
|
a1091368fb | ||
|
|
71b7c0b868 | ||
|
|
7c79714528 | ||
|
|
8d84521767 | ||
|
|
14ba358525 | ||
|
|
7ea1468a8d | ||
|
|
f72ac9a3bb | ||
|
|
f100aeda39 | ||
|
|
953a0d0583 | ||
|
|
2afe7debda | ||
|
|
d8737a5228 | ||
|
|
150a13824b | ||
|
|
830e149d0e | ||
|
|
d9e259dde4 | ||
|
|
9e6adff942 | ||
|
|
82a78546ab | ||
|
|
19c35c0b08 | ||
|
|
ade2a54369 | ||
|
|
dffbc437a6 | ||
|
|
b5697a366a | ||
|
|
0469d28c05 | ||
|
|
d5f44dd76f | ||
|
|
354d64069f | ||
|
|
3465866278 | ||
|
|
840148fbf6 | ||
|
|
34b5f9c927 | ||
|
|
75ee65f9e8 | ||
|
|
035ef26542 | ||
|
|
4ef45b9a1d | ||
|
|
990fa9498a | ||
|
|
276a2b988a | ||
|
|
ef72cddfe8 | ||
|
|
d8d3246e30 | ||
|
|
bba800eb96 | ||
|
|
ff71bf515a | ||
|
|
6ef48591a1 | ||
|
|
a7526dbf4a | ||
|
|
51d94efc9c | ||
|
|
833cc55e26 | ||
|
|
689366248d | ||
|
|
3acdffb297 | ||
|
|
fbf249580f | ||
|
|
d2e3e19831 | ||
|
|
0523b68c26 | ||
|
|
995a33a645 | ||
|
|
5c4b1f6e63 | ||
|
|
ba5865683f | ||
|
|
89a4943349 | ||
|
|
35e0e882c1 | ||
|
|
0197482618 | ||
|
|
72bad4d54c | ||
|
|
48de904ee1 | ||
|
|
f6a931d8c6 | ||
|
|
277a273ebc | ||
|
|
bd5110b951 | ||
|
|
aeecf87490 | ||
|
|
eac3f5ce5f | ||
|
|
706ee88914 | ||
|
|
7c63e81e01 | ||
|
|
99a1123e01 | ||
|
|
24d898d87a | ||
|
|
5650b56bed | ||
|
|
547cbf82c8 | ||
|
|
03ee9c7ecf | ||
|
|
ac738dae05 | ||
|
|
dda8081984 | ||
|
|
f0fc37c43b | ||
|
|
5a99de1fa9 | ||
|
|
ce956c980d | ||
|
|
c045dc244e | ||
|
|
1b11aa1d21 | ||
|
|
dac642f744 | ||
|
|
19e164b599 | ||
|
|
339ecdd817 | ||
|
|
5fe7ae1613 | ||
|
|
65f2695963 | ||
|
|
cf5e42442b | ||
|
|
e7b678d5e0 | ||
|
|
f04447837c | ||
|
|
b7d2ad2e18 | ||
|
|
f1710f1c7f | ||
|
|
37094447b1 | ||
|
|
b97d377d83 | ||
|
|
46b6528273 | ||
|
|
6c3b3acde0 | ||
|
|
91b036a4fc | ||
|
|
67cd0e5fca | ||
|
|
9dfa5da7bc | ||
|
|
9bda60bf89 | ||
|
|
9aaf20fb5e | ||
|
|
8496ceaf15 | ||
|
|
cf23e0ffea | ||
|
|
4e9e3f5abe | ||
|
|
cbab3fb561 | ||
|
|
f7bf4ac6ff | ||
|
|
df4c66b9e2 | ||
|
|
a6ee87281a | ||
|
|
90dab6831b | ||
|
|
839d5415fc | ||
|
|
29f03bf398 | ||
|
|
ec235f3efd | ||
|
|
3b25b3b342 | ||
|
|
aa89453bf6 | ||
|
|
cc97f3b331 | ||
|
|
89ec3037d4 | ||
|
|
e711f1a091 | ||
|
|
ad18e0335d | ||
|
|
67cc73b8e2 | ||
|
|
638952fad1 | ||
|
|
56ec66f90e | ||
|
|
c0bbe530f1 | ||
|
|
4bfc476e08 | ||
|
|
5d71d50d4e | ||
|
|
26700f3005 | ||
|
|
a7be9c75f9 | ||
|
|
b6f1e0d13c | ||
|
|
50630cc934 | ||
|
|
43db5bec97 | ||
|
|
affd598470 | ||
|
|
178bea07a8 | ||
|
|
fcdf95b5ec | ||
|
|
991418ce6e | ||
|
|
d34bee3359 | ||
|
|
0a3c1645e8 | ||
|
|
9dc4d78b3b | ||
|
|
bc056288d4 | ||
|
|
8aea31232b | ||
|
|
fc6c871382 | ||
|
|
14c0e4535a | ||
|
|
7ee7ba0e05 | ||
|
|
7992b883d2 | ||
|
|
5bc0c3f06f | ||
|
|
b457ecd387 | ||
|
|
997646b2db | ||
|
|
915849b83a | ||
|
|
129118b483 | ||
|
|
aa68408c53 | ||
|
|
d396a0b65c | ||
|
|
827b872cf8 | ||
|
|
6f3cae5ddf | ||
|
|
31bf92ec8e | ||
|
|
68d8aee6c3 | ||
|
|
c376f0afd2 | ||
|
|
60e5554125 | ||
|
|
241c43a894 | ||
|
|
fecc112196 | ||
|
|
dd04577994 | ||
|
|
51a299259b | ||
|
|
0a0a39880c | ||
|
|
5ad9cf3f16 | ||
|
|
d29fe5b04e | ||
|
|
20062d861d | ||
|
|
73de8b32a9 | ||
|
|
8af1301062 | ||
|
|
8c3ade2d50 | ||
|
|
752a3cd66a | ||
|
|
154ba422f2 | ||
|
|
03a9522191 | ||
|
|
c4947cad3c | ||
|
|
3f8cdafea3 | ||
|
|
fcaf8e88f8 | ||
|
|
e7ab25f819 | ||
|
|
b45a27e231 | ||
|
|
2e5b7b89af | ||
|
|
66912d3d05 | ||
|
|
ec1ac6d11c | ||
|
|
7cca8857ff | ||
|
|
e7086415f8 | ||
|
|
4a27d81da1 | ||
|
|
739425e99c | ||
|
|
cc1574d913 | ||
|
|
d7ab6e2dc1 | ||
|
|
808bedf984 | ||
|
|
357eb885b2 | ||
|
|
417e04f655 | ||
|
|
2d41bb9960 | ||
|
|
7d7c82ce91 | ||
|
|
0c3f7a806e | ||
|
|
2a38bca34b | ||
|
|
c5bd42bbf1 | ||
|
|
8c1f15a49e | ||
|
|
f9778a0aff | ||
|
|
2ac84b4320 | ||
|
|
0677443ccf | ||
|
|
2c1f3e649a | ||
|
|
4faf5428b1 | ||
|
|
b71472661b | ||
|
|
6fa111866f | ||
|
|
f7ea8df89e | ||
|
|
041225c3f2 | ||
|
|
a8d708ea1b | ||
|
|
5ae7112192 | ||
|
|
2278340c7b | ||
|
|
87abaee8a7 | ||
|
|
8f2e2b9ccd | ||
|
|
d2ecb0aad9 | ||
|
|
1126d26574 | ||
|
|
b71e299161 | ||
|
|
572ea9f27a | ||
|
|
bc298bedd5 | ||
|
|
202557630c | ||
|
|
c27d5fa162 | ||
|
|
3090557eeb | ||
|
|
6de9f7efa7 | ||
|
|
f2b8d000f7 | ||
|
|
8ee546d34d | ||
|
|
5b94c69d10 | ||
|
|
8ae192f0a9 | ||
|
|
2e1a1c31e6 | ||
|
|
238da873b2 | ||
|
|
edb5f294ef | ||
|
|
5a76e2aa58 | ||
|
|
1e20cac0da | ||
|
|
d8e411462e | ||
|
|
59e65ed4a6 | ||
|
|
4319b5e888 | ||
|
|
0d9695c3a5 | ||
|
|
359aa1a074 | ||
|
|
e608f09b53 | ||
|
|
beed10901d | ||
|
|
dab6fa5aa1 | ||
|
|
30d20f824b | ||
|
|
80cf533b05 | ||
|
|
6ff57f53f8 | ||
|
|
650446de5b | ||
|
|
347601b9fe | ||
|
|
5c8a5998d9 | ||
|
|
391e040c6a | ||
|
|
8597d9e926 | ||
|
|
a895ce666e | ||
|
|
9d113dbc13 | ||
|
|
5cc2f8f9f7 | ||
|
|
5b9ada1f8c | ||
|
|
369059fa39 | ||
|
|
9473f4e8fd | ||
|
|
685053599f | ||
|
|
a9727cf267 | ||
|
|
bc399ba9f9 | ||
|
|
81f403a9f2 | ||
|
|
332f759f96 | ||
|
|
5d5aafb7cd | ||
|
|
f42f7cfe72 | ||
|
|
96b9484f70 | ||
|
|
3bb408194d | ||
|
|
865ba94605 | ||
|
|
bb883cd228 | ||
|
|
d3e1ddd234 | ||
|
|
b9c4884af1 | ||
|
|
a6b16eedfa | ||
|
|
9d04a82c42 | ||
|
|
c9ad18894d | ||
|
|
b2b44555ca | ||
|
|
14e094b522 | ||
|
|
0dea4b7944 | ||
|
|
94a3cfa9de | ||
|
|
ed1feb9502 | ||
|
|
a4856b9a73 | ||
|
|
ff3ccde62a | ||
|
|
2a2977eca7 | ||
|
|
1d9e2ccedd | ||
|
|
dd8243a56d | ||
|
|
2c9acb655d | ||
|
|
e1191b665e | ||
|
|
e4e23c9ecd | ||
|
|
4ce2b94241 | ||
|
|
11ac253c46 | ||
|
|
1d2e85672c | ||
|
|
82b8b56aa3 | ||
|
|
4e18d7800a | ||
|
|
7efd067bd5 | ||
|
|
4c7129115c | ||
|
|
0f14ed926b | ||
|
|
be5f1546ca | ||
|
|
f3160bf6cb | ||
|
|
fa79db65b7 | ||
|
|
d134aa0c20 | ||
|
|
b9f40f326f | ||
|
|
774aeeea68 | ||
|
|
f28efce11e | ||
|
|
d03e7e2b15 | ||
|
|
56ed158814 | ||
|
|
166a9a7f8f | ||
|
|
b131e8cfa4 | ||
|
|
2e1d0e36c5 | ||
|
|
ddcf619deb | ||
|
|
8f1b66ab5a | ||
|
|
167858cd95 | ||
|
|
04e56fb94c | ||
|
|
00fce30072 | ||
|
|
423392a18a | ||
|
|
cdb34571ab | ||
|
|
3b4d28eec3 | ||
|
|
297122f47f | ||
|
|
f9ff05f16c | ||
|
|
071545987b | ||
|
|
c36a51b8e3 | ||
|
|
232bbaf6d4 | ||
|
|
a0b38a9cac | ||
|
|
d76367584c | ||
|
|
86fd77f9c4 | ||
|
|
ebaf78f5aa | ||
|
|
b484593869 | ||
|
|
a7bf6258f7 | ||
|
|
2c6bd3ee1b | ||
|
|
87ac3daecc | ||
|
|
fd7b9d766a | ||
|
|
f822761ab2 | ||
|
|
1e2cf9d476 | ||
|
|
2d930894ff | ||
|
|
30b7de7233 | ||
|
|
0d22dbc982 | ||
|
|
8446e17703 | ||
|
|
fb936f896e | ||
|
|
0d0fcbbc0b | ||
|
|
5cfbc6b91d | ||
|
|
3b1f6ce4fb | ||
|
|
fd3927ce3e | ||
|
|
bbfd4b4996 | ||
|
|
848cbe11dd | ||
|
|
f7185a618b | ||
|
|
d70dc8b88c | ||
|
|
b5f5d3f0f0 | ||
|
|
ddfc2464f3 | ||
|
|
c3402a97f3 | ||
|
|
6ee6b3c398 | ||
|
|
42a4abe4c7 | ||
|
|
0f1334c92c | ||
|
|
346d091d00 | ||
|
|
27dba9ec1b | ||
|
|
8dbf63e436 | ||
|
|
30a11c75cb | ||
|
|
49caff5759 | ||
|
|
0a7251add0 | ||
|
|
b5dac60cbb | ||
|
|
9270834631 | ||
|
|
575dd59c78 | ||
|
|
15e79c1400 | ||
|
|
c75c248086 | ||
|
|
741f6310b3 | ||
|
|
2202ec451a | ||
|
|
1830458b6b | ||
|
|
1b88430f71 | ||
|
|
bbb4c4347e | ||
|
|
0ee46ad70e | ||
|
|
243e129710 | ||
|
|
a91f204682 | ||
|
|
5bc5eec85e | ||
|
|
80d3781d81 | ||
|
|
b1e2ab4448 | ||
|
|
3e49e33f9f | ||
|
|
986840b6bf | ||
|
|
61aea888f6 | ||
|
|
c16037122b | ||
|
|
a823b909ad | ||
|
|
5d55c7ac00 | ||
|
|
75f380252e | ||
|
|
5eb67990ca | ||
|
|
017fe87e4b | ||
|
|
fc050c3ce9 | ||
|
|
b893aafae7 | ||
|
|
9c24b26d02 | ||
|
|
22d1fc98ee | ||
|
|
d88f204a15 | ||
|
|
5d0f79932a | ||
|
|
7c44e5933d | ||
|
|
4d36c60738 | ||
|
|
53c2a02124 | ||
|
|
1701a4c97c | ||
|
|
e4b6385dcc | ||
|
|
2e92ec3e4d | ||
|
|
d20485027f | ||
|
|
eb2a83b975 | ||
|
|
9344ed121f | ||
|
|
c3327e567e | ||
|
|
3ed8987779 | ||
|
|
fa8588f766 | ||
|
|
fa8ffb8e66 | ||
|
|
9da8f9cc5a | ||
|
|
af03f07cc7 | ||
|
|
5ae16b1c7c | ||
|
|
a37a7ff142 | ||
|
|
ff592758cf | ||
|
|
b04446d24d | ||
|
|
40f8c931c7 | ||
|
|
a22517adc2 | ||
|
|
821f3eced5 | ||
|
|
00ec70d425 | ||
|
|
9d48d23787 | ||
|
|
9535e82da6 | ||
|
|
fd80f953e9 | ||
|
|
05718a9f20 | ||
|
|
19b7f730b6 | ||
|
|
4f2b4cea53 | ||
|
|
e566160f1e | ||
|
|
74890050d0 | ||
|
|
0cda90cdea | ||
|
|
1a11153cd2 | ||
|
|
d5aa324ce4 | ||
|
|
09df602f3b | ||
|
|
b026c44ea8 | ||
|
|
19f765df77 | ||
|
|
4e1675033a | ||
|
|
e9439125bd | ||
|
|
a11f9e4b34 | ||
|
|
10b0ade627 | ||
|
|
1a8d6e65cf | ||
|
|
9748fceda8 | ||
|
|
7005c11ac2 | ||
|
|
88bf2ae64d | ||
|
|
cae6df1457 | ||
|
|
6682c56b9f | ||
|
|
5394613ba6 | ||
|
|
6e84b98651 | ||
|
|
af1a6a851f | ||
|
|
1516cf779d | ||
|
|
db56effd91 | ||
|
|
f3d1c8b754 | ||
|
|
abf6a37f3a | ||
|
|
e8e0032404 | ||
|
|
52734eac7e | ||
|
|
ca61be5f5a | ||
|
|
b28e3d6f18 | ||
|
|
1ab1f119b8 | ||
|
|
a332f23b11 | ||
|
|
a6798c61df | ||
|
|
8ae8f526a6 | ||
|
|
847634a0f2 | ||
|
|
bc8f201b21 | ||
|
|
e450c3f83f | ||
|
|
32910b5983 | ||
|
|
a497962153 | ||
|
|
82f8a9fc98 | ||
|
|
f5bf1667a9 | ||
|
|
a9cccb3017 | ||
|
|
47ac10ca18 | ||
|
|
d4a70d5eea | ||
|
|
e78de8568e | ||
|
|
728550cac3 | ||
|
|
81c216f1c1 | ||
|
|
17f345bfe5 | ||
|
|
ebf9139762 | ||
|
|
7de40091a3 | ||
|
|
ee5775ce05 | ||
|
|
21ef2e5dd4 | ||
|
|
283956ee93 | ||
|
|
06275a46ed | ||
|
|
ea81b1dc92 | ||
|
|
fd5cfe23db | ||
|
|
bcf30a9a05 | ||
|
|
d5122dcf50 | ||
|
|
b5e05c8c77 | ||
|
|
9434ed513e | ||
|
|
6fe3ef43d8 | ||
|
|
c1f92858a2 | ||
|
|
00b5ade787 | ||
|
|
6b94f396ff | ||
|
|
b7de1e5070 | ||
|
|
4fe6c36c49 | ||
|
|
899592ce2a | ||
|
|
8585761f42 | ||
|
|
1aa2e1a409 | ||
|
|
9e3d4a7ffe | ||
|
|
fc3de4b7fd | ||
|
|
e7f07d7d3e | ||
|
|
6e07021223 | ||
|
|
3c80b6c1f7 | ||
|
|
20b0b39b14 | ||
|
|
de59d22429 | ||
|
|
1bef1b72bc | ||
|
|
47348e2e96 | ||
|
|
0f3c427e28 | ||
|
|
9bde69b6d6 | ||
|
|
dd8b1ce955 | ||
|
|
cb884094d5 | ||
|
|
f567abdaee | ||
|
|
02757693b4 | ||
|
|
35797ca6ff | ||
|
|
5588da59b9 | ||
|
|
fbb3b814c0 | ||
|
|
2784f33b52 | ||
|
|
2656d1d2af | ||
|
|
b6359d014f | ||
|
|
a4445043c1 | ||
|
|
59169876eb | ||
|
|
d06ecdef20 | ||
|
|
4a10986b5f | ||
|
|
e81b4279b8 | ||
|
|
a6abe7fa82 | ||
|
|
10a80b2752 | ||
|
|
0131c3ad13 | ||
|
|
62f975deeb | ||
|
|
2accfbf5db | ||
|
|
7ed6fcdf04 | ||
|
|
156fc0edbe | ||
|
|
c421ff718b | ||
|
|
cc31e2db7c | ||
|
|
10114f4a9f | ||
|
|
e879216fa4 | ||
|
|
f0ea893bf4 | ||
|
|
3053b9635a | ||
|
|
a1588e5f98 | ||
|
|
1fd623311e | ||
|
|
a480ef60ea | ||
|
|
4776552605 | ||
|
|
efb7adc967 | ||
|
|
feb2af060f | ||
|
|
15526aab8c | ||
|
|
679fd464f4 | ||
|
|
c8a32b578c | ||
|
|
84f3d92790 | ||
|
|
13291c8d7b | ||
|
|
8a74504fe3 | ||
|
|
11d4761bcb | ||
|
|
72213bb80d | ||
|
|
e8aee3a276 | ||
|
|
a3bf1d59e6 | ||
|
|
d3f52c6d3d | ||
|
|
fb1126cf39 | ||
|
|
ec00792475 | ||
|
|
d7bbb0d1a7 | ||
|
|
dfd58f4aa1 | ||
|
|
179afb0340 | ||
|
|
fec9803aae | ||
|
|
75de57c102 | ||
|
|
2084b1fc37 | ||
|
|
07d4d2023a | ||
|
|
fac278150e | ||
|
|
da5fb30191 | ||
|
|
01e74c73e2 | ||
|
|
782ef7fe14 | ||
|
|
bd14848600 | ||
|
|
1c245280c7 | ||
|
|
d7f48f534b | ||
|
|
8ce320148c | ||
|
|
3eef7b583d | ||
|
|
89a32aaabd | ||
|
|
92f24bd02e |
@@ -0,0 +1,6 @@
|
||||
coverage:
|
||||
status:
|
||||
# pull-requests only
|
||||
patch:
|
||||
default:
|
||||
threshold: 0.1%
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: Any Questions on Canal
|
||||
about: Ask whatever you want to know or confusion about Canal
|
||||
|
||||
---
|
||||
|
||||
## Question
|
||||
<!-- You can ask any question about this project -->
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Bug Report for Canal
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
- [ ] I have searched the [issues](https://github.com/alibaba/canal/issues) of this repository and believe that this is not a duplicate.
|
||||
- [ ] I have checked the [FAQ](https://github.com/alibaba/canal/wiki/FAQ) of this repository and believe that this is not a duplicate.
|
||||
|
||||
### environment
|
||||
|
||||
* canal version
|
||||
* mysql version
|
||||
|
||||
### Issue Description
|
||||
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
### Expected behaviour
|
||||
|
||||
### Actual behaviour
|
||||
|
||||
|
||||
If there is an exception, please attach the exception trace:
|
||||
|
||||
```
|
||||
Just put your stack trace here!
|
||||
```
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: Feature Request of Canal
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
## Why you need it?
|
||||
<!-- Is your feature request related to a problem? Please describe in details -->
|
||||
|
||||
|
||||
## How it could be?
|
||||
<!--A clear and concise description of what you want to happen. For a computer fan, you can explain more about input of the feature, and output of it.-->
|
||||
|
||||
|
||||
## Other related information
|
||||
<!-- Add any other context or screenshots about the feature request here.-->
|
||||
@@ -16,3 +16,10 @@ jtester.properties
|
||||
.DS_Store
|
||||
*.tar.gz
|
||||
*.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/
|
||||
|
||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
language: java
|
||||
sudo: false # faster builds
|
||||
|
||||
jdk:
|
||||
- oraclejdk12
|
||||
- openjdk11
|
||||
- oraclejdk11
|
||||
- openjdk8
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
|
||||
install: true
|
||||
|
||||
script:
|
||||
- travis_wait 30 ./mvnw clean install -DskipTests=false -Dcheckstyle.skip=true -Dmaven.javadoc.skip=true
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
after_failure:
|
||||
- if [ -f canal.log ]; then echo "------TAIL of canal.log------"; tail -n 1000 canal.log; echo "------END of canal.log------"; fi
|
||||
@@ -1,99 +1,100 @@
|
||||
<div class="blog_content">
|
||||
<div class="iteye-blog-content-contain">
|
||||
[](https://travis-ci.com/alibaba/canal)
|
||||
[](https://codecov.io/gh/alibaba/canal)
|
||||

|
||||

|
||||
[](http://isitmaintained.com/project/alibaba/canal "average time to resolve an issue")
|
||||
[](http://isitmaintained.com/project/alibaba/canal "percentage of issues still open")
|
||||
|
||||
<h3>最新更新</h3>
|
||||
<ol>
|
||||
<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>
|
||||
## 背景
|
||||
|
||||
<h1>背景</h1>
|
||||
<p style="font-size: 14px;"> 早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求。不过早期的数据库同步业务,主要是基于trigger的方式获取增量变更,不过从2010年开始,阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&消费的业务,从此开启了一段新纪元。</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;">基于日志增量订阅&消费支持的业务:</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;"> 定位: 基于数据库增量日志解析,提供增量数据订阅&消费,目前主要支持了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>
|
||||
早期,阿里巴巴 B2B 公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求 ,主要是基于trigger的方式获取增量变更。从 2010 年开始,公司开始逐步尝试数据库日志解析,获取增量变更进行同步,由此衍生出了增量订阅和消费业务,从此开启一段新纪元。
|
||||
|
||||
<h1>相关文档</h1>
|
||||
当前的 canal 支持源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x
|
||||
|
||||
See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文档</a>
|
||||
基于日志增量订阅和消费的业务包括
|
||||
- 数据库镜像
|
||||
- 数据库实时备份
|
||||
- 索引构建和实时维护(拆分异构索引、倒排索引等)
|
||||
- 业务 cache 刷新
|
||||
- 带业务逻辑的增量数据处理
|
||||
|
||||
<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></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/AdminGuide">AdminGuide</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/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 href="http://alibaba.github.com/canal/release.html">ReleaseNotes</a></li>
|
||||
<li><a href="https://github.com/alibaba/canal/releases">Download</a></li>
|
||||
</ul>
|
||||
## 项目介绍
|
||||
|
||||
<h1>相关资料</h1>
|
||||
#### 名称:canal [kə'næl]
|
||||
#### 译意: 水道/管道/沟渠
|
||||
#### 产品定位: 基于数据库增量日志解析,提供增量数据订阅和消费
|
||||
#### 关键词: MySQL binlog parser / real-time / queue&topic / index build
|
||||
|
||||
* ADC阿里技术嘉年华分享ppt (放在google docs上,可能需要翻墙): <a href="https://docs.google.com/presentation/d/1MkszUPYRDkfVPz9IqOT1LLT5d9tuwde_WC8GZvjaDRg/edit?usp=sharing">ppt下载</href>
|
||||
* [与阿里巴巴的RocketMQ配合使用](https://github.com/alibaba/RocketMQ)
|
||||
## 工作原理
|
||||
|
||||
<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>
|
||||
#### MySQL主备复制原理
|
||||

|
||||
|
||||
<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>
|
||||
- MySQL master 将数据变更写入二进制日志( binary log, 其中记录叫做二进制日志事件binary log events,可以通过 show binlog events 进行查看)
|
||||
- MySQL slave 将 master 的 binary log events 拷贝到它的中继日志(relay log)
|
||||
- MySQL slave 重放 relay log 中事件,将数据变更反映它自己的数据
|
||||
|
||||
<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>
|
||||
#### canal 工作原理
|
||||

|
||||
|
||||
<pre>
|
||||
【招聘】阿里巴巴中间件团队招聘JAVA高级工程师
|
||||
岗位主要为技术型内容(非业务部门),阿里中间件整个体系对于未来想在技术上有所沉淀的同学还是非常有帮助的
|
||||
工作地点:杭州、北京均可. ps. 阿里待遇向来都是不错的,有意者可以QQ、微博私聊.
|
||||
具体招聘内容:https://job.alibaba.com/zhaopin/position_detail.htm?positionId=32666
|
||||
</pre>
|
||||
- canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议
|
||||
- MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
|
||||
- canal 解析 binary log 对象(原始为 byte 流)
|
||||
|
||||
## 重要版本更新说明
|
||||
|
||||
canal 1.1.x 版本([release_note](https://github.com/alibaba/canal/releases)),性能与功能层面有较大的突破,重要提升包括:
|
||||
|
||||
- 整体性能测试&优化,提升了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)
|
||||
|
||||
## 文档
|
||||
|
||||
- [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)
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
sh stop.sh
|
||||
|
||||
sh startup.sh
|
||||
+22
@@ -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
|
||||
@@ -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
|
||||
@@ -37,15 +37,15 @@ get_pid() {
|
||||
}
|
||||
|
||||
base=`dirname $0`/..
|
||||
pidfile=$base/bin/canal.pid
|
||||
pidfile=$base/bin/admin.pid
|
||||
if [ ! -f "$pidfile" ];then
|
||||
echo "canal is not running. exists"
|
||||
echo "canal-admin is not running. exists"
|
||||
exit
|
||||
fi
|
||||
|
||||
pid=`cat $pidfile`
|
||||
if [ "$pid" == "" ] ; then
|
||||
pid=`get_pid "appName=otter-canal"`
|
||||
pid=`get_pid "appName=canal-admin"`
|
||||
fi
|
||||
|
||||
echo -e "`hostname`: stopping canal $pid ... "
|
||||
@@ -54,7 +54,7 @@ kill $pid
|
||||
LOOPS=0
|
||||
while (true);
|
||||
do
|
||||
gpid=`get_pid "appName=otter-canal" "$pid"`
|
||||
gpid=`get_pid "appName=canal-admin" "$pid"`
|
||||
if [ "$gpid" == "" ] ; then
|
||||
echo "Oook! cost:$LOOPS"
|
||||
`rm $pidfile`
|
||||
+21
@@ -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);
|
||||
}
|
||||
}
|
||||
+15
@@ -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;
|
||||
}
|
||||
}
|
||||
+50
@@ -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;
|
||||
}
|
||||
}
|
||||
+17
@@ -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);
|
||||
}
|
||||
+49
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -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);
|
||||
}
|
||||
}
|
||||
+34
@@ -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);
|
||||
}
|
||||
}
|
||||
+92
@@ -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");
|
||||
}
|
||||
}
|
||||
+131
@@ -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);
|
||||
|
||||
}
|
||||
+323
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -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;
|
||||
}
|
||||
}
|
||||
+89
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+58
@@ -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());
|
||||
}
|
||||
}
|
||||
+181
@@ -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());
|
||||
}
|
||||
}
|
||||
+144
@@ -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"));
|
||||
}
|
||||
}
|
||||
+114
@@ -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;
|
||||
}
|
||||
}
|
||||
+107
@@ -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");
|
||||
}
|
||||
}
|
||||
+44
@@ -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;
|
||||
}
|
||||
}
|
||||
+44
@@ -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;
|
||||
}
|
||||
}
|
||||
+70
@@ -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;
|
||||
}
|
||||
}
|
||||
+110
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+153
@@ -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;
|
||||
}
|
||||
}
|
||||
+84
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+130
@@ -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;
|
||||
}
|
||||
}
|
||||
+81
@@ -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;
|
||||
}
|
||||
}
|
||||
+115
@@ -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;
|
||||
}
|
||||
}
|
||||
+18
@@ -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);
|
||||
}
|
||||
+14
@@ -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);
|
||||
}
|
||||
+34
@@ -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);
|
||||
}
|
||||
+27
@@ -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);
|
||||
}
|
||||
+15
@@ -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);
|
||||
}
|
||||
+10
@@ -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);
|
||||
}
|
||||
+54
@@ -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();
|
||||
}
|
||||
}
|
||||
+126
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+320
@@ -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;
|
||||
}
|
||||
}
|
||||
+202
@@ -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;
|
||||
}
|
||||
}
|
||||
+134
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+65
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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;
|
||||
@@ -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,.*\\..*
|
||||
#################################################
|
||||
@@ -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 |
@@ -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
+1
@@ -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}
|
||||
+1
@@ -0,0 +1 @@
|
||||
.pagination-container[data-v-cebf2f0c]{background:#fff;padding:32px 16px}.pagination-container.hidden[data-v-cebf2f0c]{display:none}
|
||||
+1
@@ -0,0 +1 @@
|
||||
.pagination-container[data-v-cebf2f0c]{background:#fff;padding:32px 16px}.pagination-container.hidden[data-v-cebf2f0c]{display:none}
|
||||
+1
@@ -0,0 +1 @@
|
||||
.line[data-v-28f0cd0f]{text-align:center}
|
||||
+1
@@ -0,0 +1 @@
|
||||
.pagination-container[data-v-38ef71f0]{background:#fff;padding:32px 16px}.pagination-container.hidden[data-v-38ef71f0]{display:none}
|
||||
+1
@@ -0,0 +1 @@
|
||||
.dashboard-container[data-v-42037c2b]{margin:30px}.dashboard-text[data-v-42037c2b]{font-size:30px;line-height:46px}
|
||||
+1
@@ -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}}
|
||||
+1
@@ -0,0 +1 @@
|
||||
.line[data-v-64c3fae5]{text-align:center}
|
||||
+1
@@ -0,0 +1 @@
|
||||
.line[data-v-2e6a6604]{text-align:center}
|
||||
+1
@@ -0,0 +1 @@
|
||||
.line[data-v-e884438a]{text-align:center}
|
||||
+1
@@ -0,0 +1 @@
|
||||
.line[data-v-756ebb70]{text-align:center}
|
||||
+1
@@ -0,0 +1 @@
|
||||
.line[data-v-0b80198c]{text-align:center}
|
||||
+1
@@ -0,0 +1 @@
|
||||
.line[data-v-35af5ff9]{text-align:center}
|
||||
+1
@@ -0,0 +1 @@
|
||||
.line[data-v-5c332416]{text-align:center}
|
||||
+1
@@ -0,0 +1 @@
|
||||
.pagination-container[data-v-38ef71f0]{background:#fff;padding:32px 16px}.pagination-container.hidden[data-v-38ef71f0]{display:none}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
@@ -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)}}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
BIN
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
+1
File diff suppressed because one or more lines are too long
+1
@@ -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}}]);
|
||||
+1
@@ -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}}]);
|
||||
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
@@ -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"})}}}]);
|
||||
+1
File diff suppressed because one or more lines are too long
+1
@@ -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){}}]);
|
||||
+1
@@ -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){}}]);
|
||||
+1
@@ -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){}}]);
|
||||
+1
@@ -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){}}]);
|
||||
+1
@@ -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}}]);
|
||||
+1
@@ -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}}]);
|
||||
+1
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user