通过Hack方式实现SDC中Stage配置联动刷新
目录
问题描述
最近项目组准备开发一个IoT平台项目,需要使用到StreamSets DataCollector组件进行数据处理。
其中的一个Stage,产品经理设计了一个如下的配置界面:

预期的展示效果是通过下拉“物实例”列表框的时候,根据所选择物实例的属性个数联动刷新“属性匹配”,而且物实例下拉框的数据是通过API获取的。
这带来2个问题:
- 如何实现下拉框列表中的数据从外部获取?
- 如何实现根据所选下拉框数据联动刷新“属性匹配”的界面?
实际上,单纯的下拉列表和联动刷新SDC是原生支持的,但是下拉列表的数据是静态配置的,而且联动刷新的界面也是预先配置的。而我们的项目需求是需要根据下拉列表中选择的物实例属性个数进行联动刷新,而不同的物实例的属性个数并不相同,因此无法做到预先配置。
所以,我们的原型设计SDC原生并不能支持。
但是产品设计并不希望修改,因此只能寻找对应的解决办法。
如何从外部获取下拉列表参数
对于下拉列表的数据从外部获取这个实现相对容易,在Stage中对于下拉列表的配置通常使用如下方式:
// 物实例下拉列表
@ConfigDef(
required = true,
type = ConfigDef.Type.MODEL,
label = "Instance",
defaultValue = "",
displayPosition = 30,
group = "DIGITALTWIN",
description = "Instance List"
)
@ValueChooserModel(DigitalTwinInstanceChooser.class)
public String instance = null;
其中,DigitalTwinInstanceChooser类是数据源,它必须实现接口com.streamsets.pipeline.api.ChooserValues,即必须实现如下接口方法:
public interface ChooserValues {
String getResourceBundle();
List<String> getValues();
List<String> getLabels();
}
其中,方法getResourceBundle()是资源国际化配置参数,getValues()为下拉列表选项中各项对应的value,getLabels()为下拉列表选项中各项在界面上显示的key。因此,为了实现下拉列表数据从外部获取,只需要在实现了接口ChooserValues的类构造方法中初始化对应数据即可,如下示例:
public class DigitalTwinInstanceChooser implements ChooserValues {
private static List<String> values = null;
private static List<String> labels = null;
public DigitalTwinInstanceChooser() {
// 只需要刷新一次
if(values != null) {
return;
}
List<DigitalTwinInstance> list = DigitalTwinStreamClient.getDigitalTwinInstanceList();
setList(list);
}
// 数据初始化
public static void setList(List<DigitalTwinInstance> list) {
if(list == null) {
return;
}
values = new ArrayList<String>(list.size());
labels = new ArrayList<String>(list.size());
for(DigitalTwinInstance dtb : list) {
if(dtb == null) {
continue;
}
values.add(dtb.getId()+ "");
labels.add(dtb.getName());
}
}
@Override
public String getResourceBundle() {
return null;
}
@Override
public List<String> getValues() {
return values;
}
@Override
public List<String> getLabels() {
return labels;
}
}
如何实现根据下拉列表选项动态刷新
在我们的这个项目需求中是需要根据下拉选中的物实例属性个数动态刷新界面的,这个在SDC中原生并不支持。一开始我没有任何思路,组里熟悉这个框架的人问了一圈,没一个人解决过类似问题。其中一位同事告诉我之前一位已经离职的同事遇到过类似的需求,但是具体怎么实现的并不清楚,只是说好像通过修改前端来完成的。虽然这个信息没有直接解决我的问题,但是却给我打开了一点思路。我们知道,在SDC的Stage配置中是实时保存的。SDC的前端使用AugularJS框架,只要用户配置参数发生了变化,就会实时通过API保存到后端,这样Stage在运行时就能获取到用户配置的对应参数。顺着这个思路,我对Stage保存参数的请求进行了抓包,经过对每一次保存请求参数和API接口的返回结果进行对比发现:前端每一次将保存参数通过API发送到后台进行保存之后会将该参数再返回给前端。于是我就脑洞大开:之所以需要将用户设置的参数再返回给前端,应该是前端需要这些参数进行界面渲染。那么,对于我这个需求,当用户选择了某个具体的物实例之后,是否可以在后端根据传递的物实例参数动态将对应的属性参数返回给前端,这样前端就可以动态渲染出相应的“属性匹配”界面了呢?但是这样的话就需要修改SDC保存Stage配置参数的源码了,报着试一试的心态于是开始了如下Hack实践。
第一步,找到保存Stage参数的API接口。在浏览器中可以看到,保存Stage配置参数的地址为:/rest/v1/pipeline/{pipelineid},于是凭直接找到了对应API接口类:datacollector\container\src\main\java\com\streamsets\datacollector\restapi\PipelineStoreResource.java,在该接口中有一个更新Pipeline的方法:
@Path("/pipeline/{pipelineId}")
@POST
@ApiOperation(value = "Update an existing Pipeline Configuration by name", response = PipelineConfigurationJson.class,
authorizations = @Authorization(value = "basic"))
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed({
AuthzRole.CREATOR, AuthzRole.ADMIN, AuthzRole.CREATOR_REMOTE, AuthzRole.ADMIN_REMOTE
})
public Response savePipeline(
@PathParam("pipelineId") String name,
@QueryParam("rev") @DefaultValue("0") String rev,
@QueryParam("description") String description,
@ApiParam(name="pipeline", required = true) PipelineConfigurationJson pipeline) throws URISyntaxException, PipelineException {
if (store.isRemotePipeline(name, rev)) {
throw new PipelineException(ContainerError.CONTAINER_01101, "SAVE_PIPELINE", name);
}
PipelineInfo pipelineInfo = store.getInfo(name);
RestAPIUtils.injectPipelineInMDC(pipelineInfo.getTitle(), pipelineInfo.getPipelineId());
PipelineConfiguration pipelineConfig = BeanHelper.unwrapPipelineConfiguration(pipeline);
PipelineConfigurationValidator validator = new PipelineConfigurationValidator(stageLibrary, name, pipelineConfig);
pipelineConfig = validator.validate();
// 在这里判断并处理用户当前返回的配置
// 1.检查下拉菜单的值是否发生变化
if(checkDtInstanceChanged(pipelineInfo, rev, pipelineConfig)) {
LOG.info("Need update DT Instance attribute!");
// 2.如果下拉菜单的值发生了变化才动态返回值
pipelineConfig = updateDigitalTwinConfig(pipelineConfig);
}
pipelineConfig = store.save(user, name, rev, description, pipelineConfig);
return Response.ok().entity(BeanHelper.wrapPipelineConfiguration(pipelineConfig)).build();
}
第二步,在接口方法中根据需求实现对应的逻辑,动态返回下拉列表中选择物实例信息。
private boolean checkDtInstanceChanged(PipelineInfo pipelineInfo, String rev, PipelineConfiguration pipelineConfig) {
try {
// 加载存储的数据
PipelineConfiguration storePipeLine = store.load(pipelineInfo.getPipelineId(), rev);
// 读取已经存储的配置参数
String storeDtInstance = getDtInstanceId(storePipeLine);
// 读取前端发送过来的配置参数
String paramDtInstance = getDtInstanceId(pipelineConfig);
LOG.info("Check DT Instance Changed, stored: {}, param: {}", storeDtInstance, paramDtInstance);
return !storeDtInstance.equals(paramDtInstance);
} catch (PipelineException e) {
e.printStackTrace();
}
return false;
}
// 从配置中读取参数
private String getDtInstanceId(PipelineConfiguration pipeline) {
if(pipeline == null) {
return "";
}
List<StageConfiguration> stageList = pipeline.getStages();
for(StageConfiguration stage : stageList) {
if(!"digitaltwin-stage".equals(stage.getLibrary())) {
continue;
}
List<Config> configList = stage.getConfiguration();
for(Config config : configList) {
if(!"config.instance".equals(config.getName())) {
continue;
}
Object value = config.getValue();
if(value == null) {
return "";
}
return value.toString();
}
}
return "";
}
// 实现动态更新前端界面
private PipelineConfiguration updateDigitalTwinConfig(PipelineConfiguration pipelineConfig) {
List<StageConfiguration> stages = pipelineConfig.getStages();
if(stages == null || stages.isEmpty()) {
return pipelineConfig;
}
for(StageConfiguration stage : stages) {
if(stage == null) {
continue;
}
if(!"digitaltwin-stage".equals(stage.getLibrary())) {
continue;
}
String resourceURL = null;
String instance = null;
List<Config> configList = stage.getConfiguration();
int size = configList.size();
List<Config> newConfigList = new ArrayList<Config>(size);
int index = -1;
String key = "config.attrs";
for(int i = 0; i < size; i++) {
Config config = configList.get(i);
if(config == null) {
continue;
}
if(key.equals(config.getName())) {
index = i;
newConfigList.add(null);
continue;
}
if("config.resourceURL".equals(config.getName())) {
resourceURL = config.getValue().toString();
}
if("config.instance".equals(config.getName())) {
instance = config.getValue().toString();
}
newConfigList.add(config);
}
if((resourceURL == null || "".equals(resourceURL.trim())
|| (instance == null || "".equals(instance.trim())))) {
return pipelineConfig;
}
// 动态读取属性列表
List<DtConfig> list = DtStreamClient.getDtInstanceAttrList(resourceURL, instance);
//newConfigList.add(new Config("config.attrs", list));
newConfigList.set(index, new Config(key, list));
stage.setConfig(newConfigList);
// 强制前端刷新界面
/*Map<String, Object> uiInfo = stage.getUiInfo();
try {
String key = "xPos";
Object xPos = uiInfo.get(key);
if(xPos != null) {
int x = Integer.valueOf(xPos.toString());
x += 1;
uiInfo.put(key, x);
}
} catch (NumberFormatException e) {
e.printStackTrace();
}*/
}
return pipelineConfig;
}
最后进行验证,竟然成功了!!!
总结
- Stage中关于“属性匹配”参数类型必须为Map结构类型:
type = ConfigDef.Type.MAP,如下:
// 动态切换属性
@ConfigDef(
required = false,
type = ConfigDef.Type.MAP,
label = "DigitalTwin Attribute Map",
displayPosition = 40,
group = "DIGITALTWIN"
)
public Map<String, String> attrs = new HashMap<String, String>();
public Map<String, String> getAttrs() {
return this.attrs;
}
- 这只是一个Hack的方式,虽然实现了业务需求,但是不推荐。应该准确定位SDC的在项目架构中功能和作用,避免出现类似的“不合理”的设计。
- 配置参数的返回顺序必须与发送参数保持一致,否则会发现第一次配置时刷新存在问题(不能正确渲染出服务端返回的属性参数,需要切换界面才能刷新)。
通过Hack方式实现SDC中Stage配置联动刷新的更多相关文章
- Soul Android app 悬浮view以及帖子中view的联动刷新逆向分析
Soul app是我司的竞品,对它的语音音乐播放同步联动的逻辑很感兴趣,于是就开启了一波逆向分析. 下面看代码,以及技术分析,直接步入正轨,哈哈. 我们根据https://github.com/xin ...
- 史上最全的CSS hack方式一览 jQuery 图片轮播的代码分离 JQuery中的动画 C#中Trim()、TrimStart()、TrimEnd()的用法 marquee 标签的使用详情 js鼠标事件 js添加遮罩层 页面上通过地址栏传值时出现乱码的两种解决方法 ref和out的区别在c#中 总结
史上最全的CSS hack方式一览 2013年09月28日 15:57:08 阅读数:175473 做前端多年,虽然不是经常需要hack,但是我们经常会遇到各浏览器表现不一致的情况.基于此,某些情况我 ...
- Flask:文件配置方式实践及其中的各种问题记录
Windows 10家庭中文版,Python 3.6.4,Flask 1.0.2, 提示: 1.请查看本文后面的“18-07-17 11:18重大纠正” ! 2.flask run命令运行时传入参数 ...
- 调用init方法 两种方式 一个是浏览器方法 一个是 xml中手工配置(load-on-startup)
调用init方法 两种方式 一个是浏览器方法 一个是 xml中手工配置(load-on-startup)
- jmeter中通过jdbc方式连接mysql数据库的配置参考
jmeter中通过jdbc方式连接mysql数据库的配置参考: Database URL=jdbc:mysql://ip:port/dbname?useUnicode=true&allowMu ...
- SpringMVC(十六):如何使用编程方式替代/WEB-INF/web.xml中的配置信息
在构建springmvc+mybatis项目时,更常用的方式是采用web.xml来配置,而且一般情况下会在web.xml中使用ContextLoaderListener加载applicationCon ...
- Springboot中以配置类方式自定义Mybatis的配置规则(如开启驼峰映射等)
什么是自定义Mybatis的配置规则? 答:即原来在mybatis配置文件中中我们配置到<settings>标签中的内容,如下第6-10行内容: 1 <?xml version=&q ...
- Eclipse中安装配置Tomcat
Eclipse(4.4.x及以上)中安装配置Tomcat 以下配置说明全部针对免安装版本 基于tomcat的安装目录和运行目录是可以不同的,本文都会进行说明 首先简单介绍一下tomcat的目录结构,一 ...
- CSS hack方式一览【转】
做前端多年,虽然不是经常需要hack,但是我们经常会遇到各浏览器表现不一致的情况.基于此,某些情况我们会极不情愿的使用这个不太友好的方式来达到大家要求的页面表现.我个人是不太推荐使用hack的,要知道 ...
随机推荐
- 我的Windows日常——Win7完美兼容tsmmc.msc的方法
操作步骤 32位操作系统: 1.将2003系统C:\WINDOWS\system32目录下的mstsmhst.dll.mstsmmc.dll.tsmmc.msc拷贝到Windows7系统中的C:\WI ...
- composer在update时提示file could not be downloaded: SSL operation failed with code 1. OpenSSL Error messages: error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO
在开发的时候,需要把依赖的服务更新到最新,然后 手动composer update一下,提示如下: failed) Update failed (The "e "https://a ...
- springboot 2.1.4 源码默认logback-spring.xml
logback-spring.xml 是由几个文件组成的,整个的一个xml为 <?xml version="1.0" encoding="UTF-8"?& ...
- 014_zk路径过滤分析
一.线上zk访问延迟特别高需要统计一段时间内的zk写入路径top10,实现如下: #!/usr/bin/env python # -*- coding:utf-8 -*- import re,trac ...
- Django2 Django MTV模板
1.MVC模型 Web服务器开发领域里著名的MVC模式,所谓MVC就是把Web应用分为模型(M),控制器(C)和视图(V)三层,他们之间以一种插件式的.松耦合的方式连接在一起,模型负责业务对象与数据库 ...
- window nginx 基础命令
在Windows下使用Nginx,我们需要掌握一些基本的操作命令,比如:启动.停止Nginx服务,重新载入Nginx等,下面我就进行一些简单的介绍.(说明:打开cmd窗口) 1.启动: C:\serv ...
- servlet(1)
servlet类分级: 1.ServletConfig接口类:理解为读取servlet配置的类,里面有四个抽象方法如下: ①getServletName:获取servlet在web.xml中的名字 ② ...
- Allow Only Ajax Requests For An Action In ASP.NET Core
ASP.NET Core offers attributes such as [HttpGet] and [HttpPost] that allow you to restrict the HTTP ...
- Ubuntu16设置Redis开机自启动
Ubuntu16设置Redis开机自启动 Ubuntu16设置Redis开机自启动 设置条件: -Ubuntu16.04 -Redis-4.0.11 在redis目录下找到 utils/redi ...
- Spring MVC 使用介绍(十六)数据验证 (三)分组、自定义、跨参数、其他
一.概述 除了依赖注入.方法参数,Bean Validation 1.1定义的功能还包括: 1.分组验证 2.自定义验证规则 3.类级别验证 4.跨参数验证 5.组合多个验证注解 6.其他 二.分组验 ...