最近在做数据中台资产管理系统,主要核心功能是数据资产发布成api或者文档,客户通过api直接获取发布的数据资产。需要一下一下几点功能:

1  界面sql编辑数据产品

2. 自动发布数据产品到api

调研后一段时间主要技术有Rocket-api和Dataway(hasor) https://www.hasor.net/doc/display/dataway

简介:

"Rocket-API" 基于spring boot 的API敏捷开发框架,通过写SQL或者 mongodb原始执行脚本代替CRUD完成开发。提升开发效率。

特性:

1. api配置化开发,不再定义Controller,Service,Dao,Mybatis,xml,Entity,VO等对象和方法.

2. 可视化界面,将入参自动封装到可执行脚本上,支持sql执行语句

3. 完全基于spring boot 2.x作为springboot项目的starter方式集成

4. 在线动态编译,无需重新,立即生效,多数据源操作(静态配置)

5.版本控制,历史记录,回滚功能

6. 代码提示:sql提示,语法提示

7. 远程一建发布到生产环境

8. 线上POSTMAN调试,保存POSTMAN信息或三方文档的自动生成,历史调用记录存储,回塑

9. 用户控制管理,安全性管理

10. 动态数据源管理

工作原理:

1.  将api信息保存到数据库,调用springboot的RequestMapperHandlerMapping.registerMapping/unregisterMapping 实现动态管理RequestMapping。

2. 依赖java 1.8的ScriptEngineManager方法,调用Groovy引擎,赋于数据处理能力以及使代码逻辑能够实现动态编译,发布,而不用重启

搭建:

源码地址: https://gitee.com/alenfive/rocket-api

需要下载2.3.5.RELEAS版本,最新亲测有bug

带入idea,修改application.yml文件的数据库信息

datasource:url: jdbc:mysql://xxxxxxx:3317/dap?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaiusername: myuserpassword: mypassworddriver-class-name: com.mysql.cj.jdbc.Driver

配置数据源:

@Component
public class DefaultDataSourceManager extends DataSourceManager {@Autowiredprivate JdbcTemplate jdbcTemplate;@Autowiredprivate MongoTemplate mongoTemplate;@PostConstructpublic void init() {Map<String, DataSourceDialect> dialects = new LinkedHashMap<>();dialects.put("mysql",new MySQLDataSource(jdbcTemplate,true));//dialects.put("postgres",new PostgreSQLDataSource(jdbcTemplate,false));//dialects.put("oracle",new OracleDataSource(jdbcTemplate,true));
//        dialects.put("mongodb",new MongoDataSource(mongoTemplate,true));super.setDialectMap(dialects);}
}

创建数据库:

CREATE TABLE `api_info` (`id` varchar(45) NOT NULL,`method` varchar(45) DEFAULT NULL,`path` varchar(100) DEFAULT NULL,`type` varchar(5) DEFAULT NULL COMMENT '类型:CODE,QL',`service` varchar(45) DEFAULT NULL,`editor` varchar(45) DEFAULT NULL,`name` varchar(200) DEFAULT NULL,`datasource` varchar(45) DEFAULT NULL,`script` text,`options` text,`create_time` varchar(45) DEFAULT NULL,`full_path` varchar(200) DEFAULT NULL,`directory_id` varchar(45) DEFAULT NULL,`update_time` varchar(45) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `unique_path_method` (`service`,`full_path`,`method`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='路径明细';CREATE TABLE `api_info_history` (`id` varchar(45) NOT NULL,`api_info_id` varchar(45) NOT NULL,`method` varchar(45) DEFAULT NULL,`path` varchar(100) DEFAULT NULL,`type` varchar(5) DEFAULT NULL COMMENT '类型:CODE,QL',`service` varchar(45) DEFAULT NULL,`editor` varchar(45) DEFAULT NULL,`name` varchar(200) DEFAULT NULL,`datasource` varchar(45) DEFAULT NULL,`script` text,`options` text,`create_time` varchar(45) DEFAULT NULL,`full_path` varchar(200) DEFAULT NULL,`directory_id` varchar(45) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='路径明细历史';CREATE TABLE `api_example` (`id` varchar(45) NOT NULL,`api_info_id` varchar(45) NOT NULL,`method` varchar(45) DEFAULT NULL,`url` text,`request_header` text,`request_body` text,`response_header` text,`response_body` text,`status` varchar(10) DEFAULT NULL,`elapsed_time` int(11) DEFAULT NULL,`options` text,`editor` varchar(45) DEFAULT NULL,`create_time` varchar(45) DEFAULT NULL,PRIMARY KEY (`id`),KEY `index_api_id` (`api_info_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='模拟数据';//在开启了spring.rocket-api.config-enabled=true才需要
CREATE TABLE `api_config` (`id` varchar(45) NOT NULL,`service` varchar(45) NOT NULL,`config_context` text,PRIMARY KEY (`id`),UNIQUE KEY `key_UNIQUE` (`id`),UNIQUE KEY `service_UNIQUE` (`service`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;CREATE TABLE `api_directory` (`id` varchar(45) NOT NULL,`name` varchar(45) DEFAULT NULL,`path` varchar(200) DEFAULT NULL,`parent_id` varchar(20) DEFAULT NULL,`service` varchar(45) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

编译,重启,访问地址:http://localhost:8080/interface-ui

操作:

创建request:

文档同步:

实现与 yapi 等文档中心的对接

远程发布:

增量同步

对同步的数据存在更新,不存在新增,不会对其他API进行清除

全量同步

对目前库进行清空,导入同步的数据,会对其他API进行清除

数据流:

服务启动:

 spring boot启动 -->QLRequestMappingFactory.init() -->注册脚本解析类dataSourceManager.setParseService(parseService)-->加载数据库api dataSourceManager.listApiInfoByEntity(ApiInfo.builder().service(service).build()) -->加载代码方式的API getPathListForCode(); -->注册mapping registerMappingForApiInfo(apiInfo) -->
requestMappingHandlerMapping.registerMapping(mappingInfo,this, targetMethod);-->AbstractHandlerMethodMapping<T>.MappingRegistry.register(mapping, handler, method);

初始化界面:

/*** Api ui 页面显示*/
@Controller
@RequestMapping("${spring.rocket-api.base-register-path:/interface-ui}")
@ConditionalOnProperty(name = "spring.rocket-api.view-enabled",havingValue = "true",matchIfMissing = true)
public class ViewController {
@GetMappingpublic String index(Model model, HttpServletRequest request){model.addAttribute("dataSourceList",dataSourceManager.getDialectMap().keySet());model.addAttribute("service", service);model.addAttribute("configEnabled",properties.isConfigEnabled());model.addAttribute("version", PackageUtils.getVersion());if (request.getRequestURI().endsWith("/")){return "redirect:"+properties.getBaseRegisterPath();}return "rocketapi/api-index";}
}

API 展现

/*** LOAD API LIST*/@GetMapping("/api-list")public ApiResult getPathList(boolean isDb) throws Exception {return  ApiResult.success(mappingFactory.getPathList(isDb).stream().map(item->{Map<String,Object> newItem = new HashMap<>();newItem.put("id",item.getId());newItem.put("groupName",item.getGroupName());newItem.put("name",item.getName());newItem.put("method",item.getMethod());newItem.put("path",item.getPath());newItem.put("options",item.getOptions());newItem.put("datasource",item.getDatasource());return newItem;}).collect(Collectors.toList()));}

获取组件工具类信息: 前端缓存,自动提示

/*** 自动完成,类型获取*/@GetMapping("/completion-items")public ApiResult provideCompletionTypes() throws Exception {String cacheKey = "completion-items-cache";CompletionResult result = null;if ((result = (CompletionResult) cache.get(cacheKey)) != null){return ApiResult.success(result);}result = new CompletionResult();Map<String,List<MethodVo>> clazzs = new LinkedHashMap<>();Map<String,String> variables = new HashMap<>();Map<String,String> syntax = new HashMap<>();Map<String,List<TableInfo>> dbInfos = new HashMap<>();result.setClazzs(clazzs);result.setVariables(variables);result.setSyntax(syntax);result.setDbInfos(dbInfos);//获取内置自定义函数变量Collection<IFunction> functionList = context.getBeansOfType(IFunction.class).values();functionList.forEach(item->{variables.put(item.getVarName(),item.getClass().getName());});//spring bean对象获取Map<String,Object> beans = context.getBeansOfType(Object.class);for (String key : beans.keySet()){buildClazz(clazzs,beans.get(key).getClass());}//本包JAVA类List<Class> classList = PackageUtil.loadClassByLoader(Thread.currentThread().getContextClassLoader());for (Class clazz : classList){buildClazz(clazzs,clazz);}//基础包 java.util java类List<String> classNames = PackageUtil.scan();for (String clazz : classNames){buildClazz(clazzs,clazz);}//常用语法提示syntax.put("foreach","for(item in ${1:collection}){\n\t\n}");syntax.put("fori","for(${1:i}=0;${1:i}<;${1:i}++){\n\t\n}");syntax.put("for","for(${1}){\n\t\n}");syntax.put("if","if(${1:condition}){\n\n}");syntax.put("ifelse","if(${1:condition}){\n\t\n}else{\n\t\n}");syntax.put("import","import ");syntax.put("continue","continue;");syntax.put("break","break;");//数据库信息获取Map<String, DataSourceDialect> dataSourceDialectMap = dataSourceManager.getDialectMap();dataSourceDialectMap.forEach((key,value)->{List<TableInfo> tableInfos = value.buildTableInfo();if (tableInfos != null){dbInfos.put(key,tableInfos);}});//常用工具类获取cache.put(cacheKey,result);return ApiResult.success(result);}

新建api:

/*** SAVE APIINFO* @param apiInfo*/@PostMapping("/api-info")public ApiResult saveOrUpdateApiInfo(@RequestBody ApiInfo apiInfo,HttpServletRequest request) {String user = loginService.getUser(request);if(StringUtils.isEmpty(user)){return ApiResult.fail("Permission denied");}apiInfo.setEditor(user);try {if (!StringUtils.isEmpty(apiInfo.getScript())){apiInfo.setScript(scriptEncrypt.encrypt(apiInfo.getScript()));}return ApiResult.success(mappingFactory.saveOrUpdateApiInfo(apiInfo));}catch (Exception e){e.printStackTrace();return ApiResult.fail(e.getMessage());}}@Transactionalpublic String saveOrUpdateApiInfo(ApiInfo apiInfo) throws Exception {if (existsPattern(apiInfo)){throw new IllegalArgumentException("method: "+apiInfo.getMethod()+" path:"+apiInfo.getPath()+" already exist");}ApiInfo dbInfo = apiInfoCache.getAll().stream().filter(item->item.getId().equals(apiInfo.getId())).findFirst().orElse(null);SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");apiInfo.setUpdateTime(sdf.format(new Date()));if (dbInfo == null){apiInfo.setType(ApiType.Ql.name());apiInfo.setCreateTime(sdf.format(new Date()));apiInfo.setService(service);apiInfo.setId(GenerateId.get().toHexString());dataSourceManager.saveApiInfo(apiInfo);}else{apiInfo.setType(dbInfo.getType());apiInfo.setCreateTime(dbInfo.getCreateTime());apiInfo.setService(dbInfo.getService());dataSourceManager.updateApiInfo(apiInfo);//取消mapping注册unregisterMappingForApiInfo(dbInfo);//清理缓存apiInfoCache.remove(dbInfo);}dbInfo = dataSourceManager.findApiInfoById(apiInfo);//入缓存apiInfoCache.put(dbInfo);//注册mappingthis.registerMappingForApiInfo(dbInfo);//存储历史saveApiHistory(dbInfo);return dbInfo.getId();}

测试api:

/*** 脚本执行*/@PostMapping("/api-info/run")public ApiResult runScript(@RequestBody RunApiReq runApiReq, HttpServletRequest request){String user = loginService.getUser(request);if(StringUtils.isEmpty(user)){return ApiResult.fail("Permission denied");}RunApiRes runApiRes = new RunApiRes();try {apiInfoContent.setIsDebug(runApiReq.isDebug());ApiInfo apiInfo = ApiInfo.builder().path(runApiReq.getPattern()).options(runApiReq.getOptions()).datasource(runApiReq.getDatasource()).script(runApiReq.getScript()).build();ApiParams apiParams = ApiParams.builder().header(decodeHeaderValue(runApiReq.getHeader())).pathVar(getPathVar(runApiReq.getPattern(),runApiReq.getUrl())).param(getParam(runApiReq.getUrl())).body(buildBody(runApiReq.getBody())).session(RequestUtils.buildSessionParams(request)).build();Object value = scriptParse.runScript(apiInfo.getScript(),apiInfo,apiParams);runApiRes.setData(value);return ApiResult.success(runApiRes);}catch (Throwable e){e.printStackTrace();return ApiResult.fail(e.getMessage(),runApiRes);}finally {runApiRes.setLogs(apiInfoContent.getLogs());apiInfoContent.removeAll();}}@Override@Transactional(rollbackFor=Exception.class)public Object runScript(String script, ApiInfo apiInfo, ApiParams apiParams) throws Throwable {Integer pageNo = buildPagerNo(apiParams);Integer pageSize = buildPagerSize(apiParams);apiParams.putParam(apiPager.getPageNoVarName(),pageNo);apiParams.putParam(apiPager.getPageSizeVarName(),pageSize);apiParams.putParam(apiPager.getIndexVarName(),apiPager.getIndexVarValue(pageSize,pageNo));try {//注入变量apiInfoContent.setApiInfo(apiInfo);apiInfoContent.setApiParams(apiParams);Bindings bindings = new SimpleBindings();apiInfoContent.setEngineBindings(bindings);for(IFunction function : functionList){bindings.put(function.getVarName(),function);}//注入属性变量buildScriptParams(bindings,apiParams);Object result = this.engineEval(script,bindings);return result;}catch (Exception e){if (e.getCause() != null && e.getCause().getCause() != null){throw e.getCause().getCause();}else{throw e;}}}@Overridepublic Object engineEval(String script,Bindings bindings) throws Throwable {try {return engine.eval(script,bindings);}catch (Exception e){if (e.getCause() != null && e.getCause().getCause() != null){throw e.getCause().getCause();}else{throw e;}}}javax.script.ScriptEngine.eval(String script, Bindings n)

文档同步:

/*** API DOC 同步*/@GetMapping("/api-doc-push")public ApiResult apiDocPush(String apiInfoId) throws Exception {Collection<ApiInfo> apiInfos = mappingFactory.getPathList(false);String result = null;if (!StringUtils.isEmpty(apiInfoId)){ApiInfo apiInfo = apiInfos.stream().filter(item->item.getId().equals(apiInfoId)).findFirst().orElse(null);result = apiDocSync.sync(apiInfo,buildLastApiExample(apiInfo.getId()));}else{for(ApiInfo apiInfo : apiInfos){result = apiDocSync.sync(apiInfo,buildLastApiExample(apiInfo.getId()));}}return ApiResult.success(result);}/*** 默认API信息接口同步,*/
@Slf4j
@Component
public class DefaultApiDocSync implements IApiDocSync {@Overridepublic String sync(ApiInfo apiInfo, ApiExample apiExample) {return "Successful push";}
}
啥也没做

源码:

总结

修改

Rocket-api 调研相关推荐

  1. 前辈做的电子地图API调研,转过来…

    Google Maps API : Google Maps API 基于Google Maps,能够使用 JavaScript 将 Google Maps 嵌入网页中.API 提供了大量实用工具用以处 ...

  2. rocketmq(三 java操作rocket API, rocketmq 幂等性)

    JAVA操作rocketmq: 1.导入rocketmq所需要的依赖: <dependency><groupId>com.alibaba.rocketmq</groupI ...

  3. 介绍一款 API 敏捷开发工具

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 作者:棒锤 xie.infoq.cn/article/b5 ...

  4. 介绍一款 API 敏捷开发工具,告别加班!

    作者:棒锤 来源:xie.infoq.cn/article/b5c3a339267e1351c6151b42a_ 初衷 用尽可能简单的方式,完成尽可能多的需求.通过约定的方式 实现统一的标准.告别加班 ...

  5. 怎样学会科学的调研并启动一个项目

    文章首发于微信公众号<有三AI> [深度学习图像项目实战-从入门到上线1]怎样学会科学的调研并启动一个项目 00 导读 每一个项目的第一步就是立项,立项需要进行充分的调研才能确定是否值得启 ...

  6. 【深度学习图像项目实战-从入门到上线1】怎样学会科学的调研并启动一个项目...

    00 导读 每一个项目的第一步就是立项,立项需要进行充分的调研才能确定是否值得启动一个项目.调研主要要做好两个方向:1,算法调研,它主要是确定可行的技术路线.更具体的说,需要清楚想做的事情是否已经到达 ...

  7. 又发现一款牛逼的 API 敏捷开发工具

    来源:xie.infoq.cn/article/b5c3a339267e1351c6151b42a   初衷 跟大家分享一个牛逼的 API 敏捷开发工具,用尽可能简单的方式,完成尽可能多的需求.通过约 ...

  8. 如何实现1分钟写一个API接口

    Rocket API 官方文档地址:https://www.yuque.com/alenfive/rocket-api 第一步,创建接口 第二步,定义接口 第三步,接口逻辑 第四步,接口访问测试 Ro ...

  9. 主流电子地图API比较 google map api, mapabc ,yahoo地图

    主流电子地图API比较 Google Maps API : Google Maps API 基于Google Maps,能够使用 JavaScript 将 Google Maps 嵌入网页中.API ...

最新文章

  1. 失败 安装scikit_scikit-learn0.22版本最新发布
  2. 【 58沈剑 架构师之路】各种SQL到底加了什么锁?
  3. docker安装zookeeper_Docker安装Zookeeper以及Zookeeper常用命令
  4. 人群频率 | gnomAD数据库简介 (一)
  5. linux nvidia 361.run,Ubuntu 16.04安装nVidia驱动失败!
  6. python socket清空接受区_用 Python 开发一个 「聊天室」
  7. App Store 审核指南
  8. velocity语法教程
  9. Ubuntu安装Qt以及配置步骤
  10. 基于 HTML5 + WebGL 的太阳系 3D 展示系统
  11. ptp精准时间协议_PTP时钟协议原理
  12. 使用echart的小指南
  13. js 声明——有无var的区别
  14. tensorflow6-7
  15. vs2015 编译出错:Failed to register output......
  16. 25000linux集群源码,一文看懂 Redis5 搭建集群
  17. Linux的学习记录。
  18. JAVA RPG游戏
  19. unity获取麦克风音量_深入探究Valve Index的耳机、麦克风设计过程
  20. ESP8266_GET请求天气预报、json解析

热门文章

  1. 中国独角兽上市潮,爱奇艺优信小米值得投资吗? | 一点财经
  2. SQL Server数据库mdf文件中了勒索病毒.FREEMAN。扩展名变为FREEMAN
  3. 互联网晚报 | 2月16日 星期三 | 小米回应裁员10%传闻;中国冬奥军团金牌数和奖牌数创新高;马斯克捐赠57亿美元特斯拉股票...
  4. CQL 函数及多深度关系节点
  5. U盘格式化了怎么恢复数据,三步操作轻松完成
  6. UnityShader 图片或者颜色混合模式详解
  7. ubuntu-CPU频率调节
  8. 10大Android手机杀毒软件
  9. UnityHDRP贴图clipping方法
  10. AutoCAD.Net二次开发 致命错误 “unhandled access violation reading xxx...”的处理经验