目录

问题描述

最近项目组准备开发一个IoT平台项目,需要使用到StreamSets DataCollector组件进行数据处理。

其中的一个Stage,产品经理设计了一个如下的配置界面:

预期的展示效果是通过下拉“物实例”列表框的时候,根据所选择物实例的属性个数联动刷新“属性匹配”,而且物实例下拉框的数据是通过API获取的。

这带来2个问题:

  1. 如何实现下拉框列表中的数据从外部获取?
  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;
}

最后进行验证,竟然成功了!!!

总结

  1. 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;
}
  1. 这只是一个Hack的方式,虽然实现了业务需求,但是不推荐。应该准确定位SDC的在项目架构中功能和作用,避免出现类似的“不合理”的设计。
  2. 配置参数的返回顺序必须与发送参数保持一致,否则会发现第一次配置时刷新存在问题(不能正确渲染出服务端返回的属性参数,需要切换界面才能刷新)。

通过Hack方式实现SDC中Stage配置联动刷新的更多相关文章

  1. Soul Android app 悬浮view以及帖子中view的联动刷新逆向分析

    Soul app是我司的竞品,对它的语音音乐播放同步联动的逻辑很感兴趣,于是就开启了一波逆向分析. 下面看代码,以及技术分析,直接步入正轨,哈哈. 我们根据https://github.com/xin ...

  2. 史上最全的CSS hack方式一览 jQuery 图片轮播的代码分离 JQuery中的动画 C#中Trim()、TrimStart()、TrimEnd()的用法 marquee 标签的使用详情 js鼠标事件 js添加遮罩层 页面上通过地址栏传值时出现乱码的两种解决方法 ref和out的区别在c#中 总结

    史上最全的CSS hack方式一览 2013年09月28日 15:57:08 阅读数:175473 做前端多年,虽然不是经常需要hack,但是我们经常会遇到各浏览器表现不一致的情况.基于此,某些情况我 ...

  3. Flask:文件配置方式实践及其中的各种问题记录

    Windows 10家庭中文版,Python 3.6.4,Flask 1.0.2, 提示: 1.请查看本文后面的“18-07-17  11:18重大纠正” ! 2.flask run命令运行时传入参数 ...

  4. 调用init方法 两种方式 一个是浏览器方法 一个是 xml中手工配置(load-on-startup)

    调用init方法 两种方式 一个是浏览器方法 一个是 xml中手工配置(load-on-startup)

  5. jmeter中通过jdbc方式连接mysql数据库的配置参考

    jmeter中通过jdbc方式连接mysql数据库的配置参考: Database URL=jdbc:mysql://ip:port/dbname?useUnicode=true&allowMu ...

  6. SpringMVC(十六):如何使用编程方式替代/WEB-INF/web.xml中的配置信息

    在构建springmvc+mybatis项目时,更常用的方式是采用web.xml来配置,而且一般情况下会在web.xml中使用ContextLoaderListener加载applicationCon ...

  7. Springboot中以配置类方式自定义Mybatis的配置规则(如开启驼峰映射等)

    什么是自定义Mybatis的配置规则? 答:即原来在mybatis配置文件中中我们配置到<settings>标签中的内容,如下第6-10行内容: 1 <?xml version=&q ...

  8. Eclipse中安装配置Tomcat

    Eclipse(4.4.x及以上)中安装配置Tomcat 以下配置说明全部针对免安装版本 基于tomcat的安装目录和运行目录是可以不同的,本文都会进行说明 首先简单介绍一下tomcat的目录结构,一 ...

  9. CSS hack方式一览【转】

    做前端多年,虽然不是经常需要hack,但是我们经常会遇到各浏览器表现不一致的情况.基于此,某些情况我们会极不情愿的使用这个不太友好的方式来达到大家要求的页面表现.我个人是不太推荐使用hack的,要知道 ...

随机推荐

  1. python使用rabbitMQ介绍五(话题模式)

    一.模式介绍 话题模式(Topic)基本思想和路由模式是一样的,只不过路由键支持模糊匹配,符号“#”匹配一个或多个词,符号“*”匹配不多不少一个词 话题模式相当于消息的模糊匹配,或者按照正则匹配.其中 ...

  2. js坚持不懈之16:使用js向HTML元素分配事件

    向 button 元素分配 onclick 事件: <!DOCTYPE html> <html> <body> <p>点击按钮就可以执行 <em& ...

  3. 安装composer时,提示 /usr/bin/env: php: 没有那个文件或目录

    今晚在Ubuntu环境上安装composer后,想查看下是否安装成功,使用composer -v,结果提示:/usr/bin/env: php: 没有那个文件或目录 现说说我的解决办法: 它提示的原因 ...

  4. android申请多个权限的正确姿势

    ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permi ...

  5. 360 随身 WiFi3 在 Ubuntu 14.04 下的使用

    由于 360 随身 WiFi3 采用 Mediaek 代号 0e8d:760c 的芯片,目前没有官方或第三方 Linux 驱动,所以造成 Linux 用户的诸多困扰. 本文给出一个迂回的解决方案:在 ...

  6. Centos7修改yum源

    1. 备份本地yum源 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo_bak 2.获取阿里yum源配置文 ...

  7. 安装Cnario Player 3.8.1.156或其他版本时提示"Warning 4154. Adobe Flash Player 13 ...not correctly installed"

    错误提示 安装Cnario Player 3.8.1.156或其他版本时, 有时会出现如下提示: Warning 4154. Adobe Flash Player 13 ...not correctl ...

  8. pytorch错误:Missing key(s) in state_dict、Unexpected key(s) in state_dict解决

    版权声明:本文为博主原创文章,欢迎转载,并请注明出处.联系方式:460356155@qq.com 在模型训练时加上: model = nn.DataParallel(model)cudnn.bench ...

  9. sql是最成功的第四代语言

    SQL发展的前世今生 很多年前,两名年轻的IBM研究员将一门关系型语言带到了数据库领域,旨在使用声明性的方式来操作数据.从Don Chamberlin和Ramond Boyce发表"SEQU ...

  10. kettle变量(param命名参数2)

    接arg参数: 通过命令行进行变量赋值和引用 定义跟界面定义相同: 赋值(转换): 运行命令到kettle目录下 pan /file:path "/param:aa="bb&quo ...