Vue+DataV驾驶仿真数据大屏可视化-问题记录

439 阅读10分钟

此文主要描述在制作大屏可视化时自己遇到的问题,做个记录。前端参考自vue-big-screen,相关数据都通过编写后端接口传来,一番修改后初步效果如下。

beihong_1.gif

1.URL传中文参数导致乱码

由于大屏可视化中,后端需要根据前端在级联选择器上选择的环境label值,来从数据库中读取对应环境的驾驶数据,但前端传来的环境label是中文值,因此服务端后台获取到的值就往往会出现乱码。解决方案有很多种。encodeURI就是一种能解决中文乱码问题的方法。 在前端页面准备参数的时候,需要对中文参数进行encode编码:

var variable = encodeURI(encodeURI("propName"))

之后,在服务端后台程序代码中用java.net.Decoder进行解码,从而得到中文参数的真实值。

    public R getEnvironmentInfo(@PathVariable String environment) throws Exception {
        environment = URLDecoder.decode(environment, "UTF-8");
	// ...
}

为什么前端要进行两次encode: 容器默认解码时采用的编码是容器的默认编码,可能是UTF-8,GBK,也可能是其他编码方式。这与你的应用的编码方式未必会一致。所以你直接获取的话可能会出现乱码。如果不方便改容器默认编码方式,或者应用程序本身就有多种编码方式的话,还是最好采取“前端两次encode——后端一次decode”的途径获取中文参数

2.Vue前端赋值成功,但DataV相关图表缺并没有更新

这就是没细心看文档的缺点了。dataV文档中,有个用前必看,里面的状态跟新说明了这一问题,dataV里面的组件props均未设置deep监听,刷新props时,要直接生成新的props对象(基础数据类型除外),或完成赋值操作后使用ES6拓展运算符生成新的props对象,以DataV组件的config数据更新为例,即this.config = { ...this.config }

3.element-ui中级联选择器(Cascader)出现空白子菜单

原因分析: 由于element-ui中cascader每个子项均有children数组,而数据是从后端传递过来的,然后转换成json传递到前端的时候。就会出现最底层的子项中的children 为空数组,这样就会产生空级联的情况。 解决方案: 这种情况下,前端或者后端均有对应的解决方案。后端的话,直接把最里面一层子菜单的children数组改成null值做下处理就好了;或者前端也可以在js代码中,同样使用递归的方式,将最底层中的children数组设为undefined,就完事了。 image.png 之后,对于传入environment的不同label值,调用Vue的watch方法对其进行监听,便可通知后端返回对应的数据并自动刷新在页面上。

4.Vue引入全局或局部CSS文件

由于可视化大屏中的图表包含了DataV,Echarts,element-ui等多个框架的组件,因此需要通过调整各个组件的CSS文件来统一风格。 全局中引入: 在main.js中,引入全局CSS。

import './assets/styles/element-variables.scss'
import '@/assets/styles/index.scss'

局部组件中引入: 在局部组件的style标签内引入,加上scoped注解。

<style lang="scss" scoped>
 @import './scss/style.scss';
</style>

要注意的是:局部组件内引入不能用@代理路径。

5.PO BO VO DTO POJO DAO DO的概念区分

由于可视化大屏中一些组件需要的数据只是后端数据库表中的一部分,因此一般是需要编写相应的VO来显示给前端页面的,否则数据库表结构的所有字段就一览无余地展示到了前端,会给后台安全带来很大的隐患,并且无法在网络传输中剥离冗余信息提高了用户的带宽成本。 这里引用一下 知乎大佬的回答 ,来对PO BO VO DTO POJO DAO DO的概念进行区分。

PO 是 Persistant Object 的缩写,用于表示数据库中的一条记录映射成的 java 对象。PO 仅仅用于表示数据,没有任何数据操作。通常遵守 Java Bean 的规范,拥有 getter/setter 方法。

DAO 是 Data Access Object 的缩写,用于表示一个数据访问对象。使用 DAO 访问数据库,包括插入、更新、删除、查询等操作,与 PO 一起使用。DAO 一般在持久层,完全封装数据库操作,对外暴露的方法使得上层应用不需要关注数据库相关的任何信息。

VO 是 Value Object 的缩写,用于表示一个与前端进行交互的 java 对象。有的朋友也许有疑问,这里可不可以使用 PO 传递数据?实际上,这里的 VO 只包含前端需要展示的数据即可,对于前端不需要的数据,比如数据创建和修改的时间等字段,出于减少传输数据量大小和保护数据库结构不外泄的目的,不应该在 VO 中体现出来。通常遵守 Java Bean 的规范,拥有 getter/setter 方法。DTO 是 Data Transfer Object 的缩写,用于表示一个数据传输对象。

DTO 通常用于不同服务或服务不同分层之间的数据传输。DTO 与 VO 概念相似,并且通常情况下字段也基本一致。但 DTO 与 VO 又有一些不同,这个不同主要是设计理念上的,比如 API 服务需要使用的 DTO 就可能与 VO 存在差异。通常遵守 Java Bean 的规范,拥有 getter/setter 方法。

BO 是 Business Object 的缩写,用于表示一个业务对象。BO 包括了业务逻辑,常常封装了对 DAO、RPC 等的调用,可以进行 PO 与 VO/DTO 之间的转换。BO 通常位于业务层,要区别于直接对外提供服务的服务层:BO 提供了基本业务单元的基本业务操作,在设计上属于被服务层业务流程调用的对象,一个业务流程可能需要调用多个 BO 来完成。

POJO 是 Plain Ordinary Java Object 的缩写,表示一个简单 java 对象。上面说的 PO、VO、DTO 都是典型的 POJO。而 DAO、BO 一般都不是 POJO,只提供一些调用方法。

6.CompletableFuture异步编排

在驾驶仿真数据大屏可视化页面中,需要使用人-车-路-环境信息管理模块、实验数据采集模块等多个模块来完成各类图表的绘制,如果每个图表服务都顺序的执行,需要消耗较多的时间,前端用户可能无法得到及时的响应,因此考虑采用多线程来充分利用硬件资源,降低响应时间。并且各个图表绘制有一定的依赖关系,例如需要先获取到Vue前端页面中级联选择器选择的环境label,得到其分别对应的人-车-路-环境信息和驾驶仿真信息,才能进一步计算每个驾驶员在不同次仿真实验中的平均车速等数据,再进一步根据平均车速、加速度来计算道路路段安全指数等数据,因此对不同的任务需要进行异步编排。 我们知道Future是Java5添加的类,用来描述一个异步计算的结果。可以用isDone方法来检查计算是否完成,或者使用get阻塞住调用线程,直至计算完成返回结果,也可以用cancel方法来停止任务的执行。但是它也有不足之处,它对于结果的获取只能通过阻塞或轮询的方式得到任务结果,也就是get()方法。阻塞的方式与我们理解的异步编程其实是相违背的,而轮询又会耗无谓的CPU资源。而且还不能及时得到计算结果。 因此在Java8中,新增了一个类用来做异步编排,它就是CompletableFuture,提供了非常强大的Future的扩展功能,可以帮助简化异步编程的复杂性,提供了函数式编程能力

6.1 runAsync 和 supplyAsync方法

CompletableFuture包含了50个方法左右,用于简化异步编排。以下四个静态方法用来为一段异步执行的代码创建CompletableFuture对象:

public static CompletableFuture<Void> 	runAsync(Runnable runnable)
public static CompletableFuture<Void> 	runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> 	supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> 	supplyAsync(Supplier<U> supplier, Executor executor)

方法的参数类型都是函数式接口,所以可以使用lambda表达式实现异步任务。 runAsync方法:以Runnabel函数式接口类型为参数,所以CompletableFuture的计算结果为空。 supplyAsync方法:以Supplier<U>函数式接口类型为参数,CompletableFuture的计算结果类型为U。 如果方法以Async结尾并且没有指定Executor的方法会使用默认线程池ForkJoinPool.commonPool() 作为它的线程池执行异步代码。

6.2 线程串行化

  • thenRun:不能获取上一步的执行结果
  • thenAcceptAsync:能接受上一步结果,但是无返回值
  • thenApplyAsync:能接受上一步结果,有返回值

6.3 计算结果完成时的回调方法——whenComplete方法

当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的Action。主要是下面的方法:

//可以处理异常,无返回值
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
//可以处理异常,有返回值
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)

可以看到Action的类型是BiConsumer<? super T,? super Throwable>它可以处理正常的计算结果,或者异常情况。

6.4 执行任务完成时对结果的处理——handle方法

handle 方法和 thenApply 方法处理方式基本一样。不同的是 handle 是在任务完成后再执行,还可以处理异常的任务。thenApply 只可以执行正常的任务,任务出现异常则不执行 thenApply 方法。

public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor)

6.5 组合任务

  • thenCombine 方法

会把 两个 CompletionStage 的任务都执行完成后,把两个任务的结果一块交给 thenCombine 来处理。

public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn,Executor executor);

当两个CompletionStage都执行完成后,把结果一块交给thenAcceptBoth来进行消耗

public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action,     Executor executor);
  • applyToEither 方法

两个CompletionStage,谁执行返回的结果快,我就用那个CompletionStage的结果进行下一步的转化操作。

public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? sup
  • acceptEither 方法

两个CompletionStage,谁执行返回的结果快,我就用那个CompletionStage的结果进行下一步的消耗操作。

public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? supe
  • runAfterEither 方法

两个CompletionStage,任何一个完成了都会执行下一步的操作(Runnable)

public CompletionStage<Void> runAfterEither(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action,Executor executor);
  • runAfterBoth 方法

两个CompletionStage,都完成了计算才会执行下一步的操作(Runnable)

public CompletionStage<Void> runAfterBoth(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action,Executor 
  • thenCompose 方法

thenCompose 方法允许你对两个 CompletionStage 进行流水线操作,第一个操作完成时,将其结果作为参数传递给第二个操作。

public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) ;
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage