Rocket-api 调研
最近在做数据中台资产管理系统,主要核心功能是数据资产发布成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 调研相关推荐
- 前辈做的电子地图API调研,转过来…
Google Maps API : Google Maps API 基于Google Maps,能够使用 JavaScript 将 Google Maps 嵌入网页中.API 提供了大量实用工具用以处 ...
- rocketmq(三 java操作rocket API, rocketmq 幂等性)
JAVA操作rocketmq: 1.导入rocketmq所需要的依赖: <dependency><groupId>com.alibaba.rocketmq</groupI ...
- 介绍一款 API 敏捷开发工具
点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 作者:棒锤 xie.infoq.cn/article/b5 ...
- 介绍一款 API 敏捷开发工具,告别加班!
作者:棒锤 来源:xie.infoq.cn/article/b5c3a339267e1351c6151b42a_ 初衷 用尽可能简单的方式,完成尽可能多的需求.通过约定的方式 实现统一的标准.告别加班 ...
- 怎样学会科学的调研并启动一个项目
文章首发于微信公众号<有三AI> [深度学习图像项目实战-从入门到上线1]怎样学会科学的调研并启动一个项目 00 导读 每一个项目的第一步就是立项,立项需要进行充分的调研才能确定是否值得启 ...
- 【深度学习图像项目实战-从入门到上线1】怎样学会科学的调研并启动一个项目...
00 导读 每一个项目的第一步就是立项,立项需要进行充分的调研才能确定是否值得启动一个项目.调研主要要做好两个方向:1,算法调研,它主要是确定可行的技术路线.更具体的说,需要清楚想做的事情是否已经到达 ...
- 又发现一款牛逼的 API 敏捷开发工具
来源:xie.infoq.cn/article/b5c3a339267e1351c6151b42a 初衷 跟大家分享一个牛逼的 API 敏捷开发工具,用尽可能简单的方式,完成尽可能多的需求.通过约 ...
- 如何实现1分钟写一个API接口
Rocket API 官方文档地址:https://www.yuque.com/alenfive/rocket-api 第一步,创建接口 第二步,定义接口 第三步,接口逻辑 第四步,接口访问测试 Ro ...
- 主流电子地图API比较 google map api, mapabc ,yahoo地图
主流电子地图API比较 Google Maps API : Google Maps API 基于Google Maps,能够使用 JavaScript 将 Google Maps 嵌入网页中.API ...
最新文章
- 失败 安装scikit_scikit-learn0.22版本最新发布
- 【 58沈剑 架构师之路】各种SQL到底加了什么锁?
- docker安装zookeeper_Docker安装Zookeeper以及Zookeeper常用命令
- 人群频率 | gnomAD数据库简介 (一)
- linux nvidia 361.run,Ubuntu 16.04安装nVidia驱动失败!
- python socket清空接受区_用 Python 开发一个 「聊天室」
- App Store 审核指南
- velocity语法教程
- Ubuntu安装Qt以及配置步骤
- 基于 HTML5 + WebGL 的太阳系 3D 展示系统
- ptp精准时间协议_PTP时钟协议原理
- 使用echart的小指南
- js 声明——有无var的区别
- tensorflow6-7
- vs2015 编译出错:Failed to register output......
- 25000linux集群源码,一文看懂 Redis5 搭建集群
- Linux的学习记录。
- JAVA RPG游戏
- unity获取麦克风音量_深入探究Valve Index的耳机、麦克风设计过程
- ESP8266_GET请求天气预报、json解析
热门文章
- 中国独角兽上市潮,爱奇艺优信小米值得投资吗? | 一点财经
- SQL Server数据库mdf文件中了勒索病毒.FREEMAN。扩展名变为FREEMAN
- 互联网晚报 | 2月16日 星期三 | 小米回应裁员10%传闻;中国冬奥军团金牌数和奖牌数创新高;马斯克捐赠57亿美元特斯拉股票...
- CQL 函数及多深度关系节点
- U盘格式化了怎么恢复数据,三步操作轻松完成
- UnityShader 图片或者颜色混合模式详解
- ubuntu-CPU频率调节
- 10大Android手机杀毒软件
- UnityHDRP贴图clipping方法
- AutoCAD.Net二次开发 致命错误 “unhandled access violation reading xxx...”的处理经验