canal admin (#1998)

* 增加注解

* modify

* 文件大小写问题
This commit is contained in:
rewerma
2019-07-16 21:47:53 +08:00
committed by GitHub
parent 4c0d82fb98
commit 10f4ef6acd
33 changed files with 452 additions and 59 deletions

View File

@@ -18,9 +18,4 @@ public class CanalAdminApplication {
application.setBannerMode(Banner.Mode.OFF);
application.run(args);
}
// public static void main(String[] args) throws Exception {
// UserAgent userAgent = new UserAgent();
//// HelloAgent helloAgent = new HelloAgent
// }
}

View File

@@ -13,6 +13,12 @@ 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 {

View File

@@ -17,6 +17,12 @@ 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 {

View File

@@ -7,6 +7,12 @@ import org.springframework.web.bind.annotation.*;
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 {
@@ -14,11 +20,24 @@ public class CanalConfigController {
@Autowired
CanalConfigService canalConfigService;
/**
* 获取配置信息
*
* @param env 环境变量
* @return 配置信息
*/
@GetMapping(value = "/config")
public BaseModel<CanalConfig> canalConfig(@PathVariable String env) {
return BaseModel.getInstance(canalConfigService.getCanalConfig());
}
/**
* 修改配置
*
* @param canalConfig 配置信息对象
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/config")
public BaseModel<String> updateConfig(@RequestBody CanalConfig canalConfig, @PathVariable String env) {
canalConfigService.updateContent(canalConfig);

View File

@@ -10,6 +10,12 @@ import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
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 {
@@ -17,45 +23,103 @@ public class CanalInstanceController {
@Autowired
CanalInstanceService canalInstanceConfigService;
/**
* 实例配置列表
*
* @param canalInstanceConfig 查询对象
* @param env 环境变量
* @return 实例列表
*/
@GetMapping(value = "/instances")
public BaseModel<List<CanalInstanceConfig>> nodeServers(CanalInstanceConfig canalInstanceConfig,
@PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.findList(canalInstanceConfig));
}
/**
* 保存实例配置
*
* @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/{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}")
public BaseModel<Boolean> start(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(canalInstanceConfigService.remoteOperation(id, null, "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"));
}
/**
* 获取远程实例运行日志
*
* @param id 实例配置id
* @param nodeId 节点id
* @param env 环境变量
* @return 实例日志信息
*/
@GetMapping(value = "/instance/log/{id}/{nodeId}")
public BaseModel<Map<String, String>> start(@PathVariable Long id, @PathVariable Long nodeId,
@PathVariable String env) {

View File

@@ -9,6 +9,12 @@ import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.NodeServer;
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 {
@@ -16,49 +22,113 @@ public class NodeServerController {
@Autowired
NodeServerService nodeServerService;
/**
* 获取所有节点信息列表
*
* @param nodeServer 筛选条件
* @param env 环境变量
* @return 节点信息列表
*/
@GetMapping(value = "/nodeServers")
public BaseModel<List<NodeServer>> nodeServers(NodeServer nodeServer, @PathVariable String env) {
return BaseModel.getInstance(nodeServerService.findList(nodeServer));
}
/**
* 保存节点信息
*
* @param nodeServer 节点信息
* @param env 环境变量
* @return 是否成功
*/
@PostMapping(value = "/nodeServer")
public BaseModel<String> save(@RequestBody NodeServer nodeServer, @PathVariable String env) {
nodeServerService.save(nodeServer);
return BaseModel.getInstance("success");
}
/**
* 获取节点信息详情
*
* @param id 节点信息id
* @param env 环境变量
* @return 检点信息
*/
@GetMapping(value = "/nodeServer/{id}")
public BaseModel<NodeServer> detail(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(nodeServerService.detail(id));
}
/**
* 修改节点信息
*
* @param nodeServer 节点信息
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/nodeServer")
public BaseModel<String> update(@RequestBody NodeServer nodeServer, @PathVariable String env) {
nodeServerService.update(nodeServer);
return BaseModel.getInstance("success");
}
/**
* 删除节点信息
*
* @param id 节点信息id
* @param env 环境变量
* @return 是否成功
*/
@DeleteMapping(value = "/nodeServer/{id}")
public BaseModel<String> delete(@PathVariable Long id, @PathVariable String env) {
nodeServerService.delete(id);
return BaseModel.getInstance("success");
}
/**
* 获取远程节点运行状态
*
* @param ip 节点ip
* @param port 节点端口
* @param env 环境变量
* @return 状态信息
*/
@GetMapping(value = "/nodeServer/status")
public BaseModel<Integer> status(@RequestParam String ip, @RequestParam Integer port, @PathVariable String env) {
return BaseModel.getInstance(nodeServerService.remoteNodeStatus(ip, port));
}
/**
* 启动远程节点
*
* @param id 节点id
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/nodeServer/start/{id}")
public BaseModel<Boolean> start(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(nodeServerService.remoteOperation(id, "start"));
}
/**
* 获取远程节点日志
*
* @param id 节点id
* @param env 环境变量
* @return 节点日志
*/
@GetMapping(value = "/nodeServer/log/{id}")
public BaseModel<String> log(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(nodeServerService.remoteCanalLog(id));
}
/**
* 关闭远程节点
*
* @param id 节点id
* @param env 环境变量
* @return 是否成功
*/
@PutMapping(value = "/nodeServer/stop/{id}")
public BaseModel<Boolean> stop(@PathVariable Long id, @PathVariable String env) {
return BaseModel.getInstance(nodeServerService.remoteOperation(id, "stop"));

View File

@@ -15,6 +15,12 @@ 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 {
@@ -22,11 +28,18 @@ public class UserController {
public static final LoadingCache<String, User> loginUsers = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(key -> null);
.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());
@@ -44,6 +57,13 @@ public class UserController {
}
}
/**
* 获取用户信息
*
* @param token token
* @param env 环境变量
* @return 用户信息
*/
@GetMapping(value = "/info")
public BaseModel<User> info(@RequestParam String token, @PathVariable String env) {
User user = loginUsers.getIfPresent(token);
@@ -57,6 +77,14 @@ public class UserController {
}
}
/**
* 修改用户信息
*
* @param user 用户信息
* @param env 环境变量
* @param httpServletRequest httpServletRequest
* @return 是否成功
*/
@PutMapping(value = "")
public BaseModel<String> update(@RequestBody User user, @PathVariable String env,
HttpServletRequest httpServletRequest) {
@@ -66,6 +94,12 @@ public class UserController {
return BaseModel.getInstance("success");
}
/**
* 用户退出
*
* @param env 环境变量
* @return 是否成功
*/
@PostMapping(value = "/logout")
public BaseModel<String> logout(@PathVariable String env) {
return BaseModel.getInstance("success");

View File

@@ -10,11 +10,23 @@ 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)

View File

@@ -1,26 +1,90 @@
package com.alibaba.otter.canal.admin.jmx;
/**
* Canal Server JMX MBean
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
public interface CanalServerMXBean {
/**
* 获取Canal Server状态
*
* @return 状态代码
*/
int getStatus();
/**
* 启动Canal Server
*
* @return 是否成功
*/
boolean start();
/**
* 停止Canal Server
*
* @return 是否成功
*/
boolean stop();
/**
* 重启Canal Server
*
* @return 是否成功
*/
boolean restart();
/**
* 退出Canal Server(关闭进程)
*
* @return 是否成功
*/
boolean exit();
/**
* 通过实例名启动实例
*
* @param destination 实例名
* @return 是否成功
*/
boolean startInstance(String destination);
/**
* 通过实例名关闭实例
*
* @param destination 实例名
* @return 是否成功
*/
boolean stopInstance(String destination);
/**
* 通过实例名重启实例
*
* @param destination 实例名
* @return 是否成功
*/
boolean reloadInstance(String destination);
/**
* 获取所有当前节点下运行中的实例
*
* @return 实例信息
*/
String getRunningInstances();
/**
* 获取Canal Server日志(末尾100行)
*
* @return 日志信息
*/
String canalLog();
/**
* 通过实例名获取实例日志(末尾100行)
*
* @return 日志信息
*/
String instanceLog(String destination);
}

View File

@@ -14,6 +14,12 @@ import java.net.SocketTimeoutException;
import java.util.concurrent.*;
import java.util.function.Function;
/**
* Canal Server JMX 控制层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
public class JMXConnection {
private static final Logger logger = LoggerFactory.getLogger(JMXConnection.class);
@@ -28,6 +34,15 @@ public class JMXConnection {
this.port = port;
}
/**
* 执行相关操作
*
* @param ip jmx ip
* @param port jmx port
* @param function 执行方法
* @param <R> 返回泛型
* @return 执行结果
*/
public static <R> R execute(String ip, int port, Function<CanalServerMXBean, R> function) {
JMXConnection jmxConnection = new JMXConnection(ip, port);
try {
@@ -41,6 +56,9 @@ public class JMXConnection {
return null;
}
/**
* 连接远程jmx
*/
public void connect() {
try {
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + ip + ":" + port + "/jmxrmi");
@@ -57,6 +75,15 @@ public class JMXConnection {
}
}
/**
* 带超时的jmx连接器
*
* @param url jmx url
* @param timeout 超时时间
* @param unit 超时时间单位
* @return JMXConnector
* @throws IOException 连接异常
*/
private static JMXConnector connectWithTimeout(final JMXServiceURL url, long timeout,
TimeUnit unit) throws IOException {
final BlockingQueue<Object> mailbox = new ArrayBlockingQueue<>(1);
@@ -108,6 +135,11 @@ public class JMXConnection {
private static final ThreadFactory daemonThreadFactory = new DaemonThreadFactory();
/**
* 获取MBean
*
* @return canalServerMXBean
*/
public CanalServerMXBean getCanalServerMXBean() {
if (jmxc == null) {
connect();
@@ -115,6 +147,9 @@ public class JMXConnection {
return canalServerMXBean;
}
/**
* 关闭jmx连接
*/
public void close() {
try {
if (jmxc != null) {

View File

@@ -1,5 +1,11 @@
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;

View File

@@ -7,6 +7,12 @@ 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 {

View File

@@ -7,6 +7,12 @@ import javax.persistence.Id;
import javax.persistence.Transient;
import java.util.Date;
/**
* Canal实例配置信息实体类
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Entity
public class CanalInstanceConfig extends Model {
@@ -30,7 +36,7 @@ public class CanalInstanceConfig extends Model {
private Date modifiedTime;
@Transient
private Long nodeId;
private Long nodeId;
@Transient
private String nodeIp;

View File

@@ -9,6 +9,12 @@ import javax.persistence.MappedSuperclass;
import javax.persistence.OptimisticLockException;
import java.lang.reflect.Field;
/**
* EBean Model扩展类
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@MappedSuperclass
public abstract class Model extends io.ebean.Model {

View File

@@ -8,6 +8,12 @@ import javax.persistence.Table;
import io.ebean.Finder;
/**
* 节点信息实体类
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Entity
@Table(name = "canal_node_server")
public class NodeServer extends Model {

View File

@@ -8,9 +8,12 @@ import javax.persistence.Table;
import javax.persistence.Transient;
import java.util.Date;
/**
* 用户信息实体类
*/
@Entity
@Table(name = "canal_user")
public class User extends Model{
public class User extends Model {
public static final UserFinder find = new UserFinder();

View File

@@ -5,6 +5,12 @@ import com.alibaba.otter.canal.admin.model.CanalInstanceConfig;
import java.util.List;
import java.util.Map;
/**
* Canal实例配置信息业务层接口
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
public interface CanalInstanceService {
List<CanalInstanceConfig> findList(CanalInstanceConfig canalInstanceConfig);

View File

@@ -5,6 +5,12 @@ import org.springframework.stereotype.Service;
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
*/
@Service
public class CanalConfigServiceImpl implements CanalConfigService {

View File

@@ -15,6 +15,12 @@ import com.alibaba.otter.canal.admin.service.CanalInstanceService;
import io.ebean.Query;
/**
* Canal实例配置信息业务层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Service
public class CanalInstanceServiceImpl implements CanalInstanceService {

View File

@@ -20,6 +20,12 @@ import com.alibaba.otter.canal.admin.service.NodeServerService;
import io.ebean.Query;
/**
* 节点信息业务层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Service
public class NodeServerServiceImpl implements NodeServerService {

View File

@@ -6,6 +6,12 @@ import com.alibaba.otter.canal.admin.service.UserService;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
/**
* 用户信息业务层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
@Service
public class UserServiceImpl implements UserService {

View File

@@ -1,11 +0,0 @@
package com.alibaba.otter.canal.admin;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = { CanalAdminApplication.class })
public class BaseTest {
}

View File

@@ -1,7 +0,0 @@
package com.alibaba.otter.canal.admin;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TestApplication {
}

View File

@@ -1,21 +0,0 @@
package com.alibaba.otter.canal.admin.service;
import com.alibaba.otter.canal.admin.BaseTest;
import com.alibaba.otter.canal.admin.model.NodeServer;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
public class NodeServerServiceTest extends BaseTest {
@Autowired
NodeServerService nodeServerService;
@Test
public void findList() {
List<NodeServer> list = nodeServerService.findList(null);
Assert.assertNotNull(list);
}
}

View File

@@ -1,12 +0,0 @@
spring.datasource:
url: jdbc:mysql://127.0.0.1:3306/canal_manager?useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: 121212
driver-class-name: com.mysql.jdbc.Driver
hikari:
maximum-pool-size: 10
minimum-idle: 1
logging:
level:
com.alibaba.otter.canal.admin: DEBUG

View File

@@ -16,6 +16,12 @@ import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
/**
* Canal Server Agent 用于远程JMX调用
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
public class CanalServerAgent implements NotificationListener {
private static final Logger logger = LoggerFactory.getLogger(CanalServerBean.class);

View File

@@ -14,6 +14,12 @@ import org.slf4j.LoggerFactory;
import javax.management.NotificationBroadcasterSupport;
import java.util.Map;
/**
* Canal配置信息业务层
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
public class CanalServerBean extends NotificationBroadcasterSupport implements CanalServerMXBean {
private static final Logger logger = LoggerFactory.getLogger(CanalServerBean.class);

View File

@@ -1,26 +1,90 @@
package com.alibaba.otter.canal.deployer.mbean;
/**
* Canal Server JMX MBean
*
* @author rewerma 2019-07-13 下午05:12:16
* @version 1.0.0
*/
public interface CanalServerMXBean {
/**
* 获取Canal Server状态
*
* @return 状态代码
*/
int getStatus();
/**
* 启动Canal Server
*
* @return 是否成功
*/
boolean start();
/**
* 停止Canal Server
*
* @return 是否成功
*/
boolean stop();
/**
* 重启Canal Server
*
* @return 是否成功
*/
boolean restart();
/**
* 退出Canal Server(关闭进程)
*
* @return 是否成功
*/
boolean exit();
/**
* 通过实例名启动实例
*
* @param destination 实例名
* @return 是否成功
*/
boolean startInstance(String destination);
/**
* 通过实例名关闭实例
*
* @param destination 实例名
* @return 是否成功
*/
boolean stopInstance(String destination);
/**
* 通过实例名重启实例
*
* @param destination 实例名
* @return 是否成功
*/
boolean reloadInstance(String destination);
/**
* 获取所有当前节点下运行中的实例
*
* @return 实例信息
*/
String getRunningInstances();
/**
* 获取Canal Server日志(末尾100行)
*
* @return 日志信息
*/
String canalLog();
/**
* 通过实例名获取实例日志(末尾100行)
*
* @return 日志信息
*/
String instanceLog(String destination);
}