AmazingDashboard - 可扩展的数据可视化平台
AmazingDashboard 指标可视化平台
本项目基于 HTML5 + AngularJS 的前端技术栈,结合 Dubbo 微服务架构 的后端服务,构建面向桌面/移动多端的数据可视化平台。
平台的核心特点是其可扩展的插件化指标系统,通过声明式配置即可新增图表控件,无需修改核心框架代码。


前端程序:DashboardClient 后端程序:DashboardService
技术架构
整体架构图
┌─────────────────────────────────────────────────────────┐
│ 前端展现层 │
│ AngularJS 1.5 + RequireJS + Bootstrap + ECharts │
├─────────────────────────────────────────────────────────┤
│ 核心框架 (Core Framework) │
│ ├── 控件基类 (Chart Base) │
│ ├── 插件加载器 (Plugin Loader) │
│ ├── 布局管理器 (Layout Manager) │
│ ├── 数据代理 (Data Proxy) │
│ └── 事件总线 (Event Bus) │
├─────────────────────────────────────────────────────────┤
│ 可扩展插件系统 (Plugin System) │
│ ├── ECharts 系列 (柱状图、饼图、仪表盘等) │
│ ├── 通用控件 (标签、计数器、表格等) │
│ ├── 项目控件 (业务定制) │
│ └── 第三方集成 (D3.js、Three.js) │
├─────────────────────────────────────────────────────────┤
│ 功能模块 (Modules) │
│ ├── 设计器 (Editor) - 仪表盘设计 │
│ ├── 播放器 (Player) - 仪表盘展示 │
│ ├── 数据集 (DataSet) - 数据源配置 │
│ ├── 模式 (Schema) - 数据结构定义 │
│ └── 页面管理 (Page Management) │
└─────────────────────────────────────────────────────────┘
↓ HTTP/JSON
┌─────────────────────────────────────────────────────────┐
│ 后端服务层 │
│ Spring Boot + Dubbo + Zookeeper │
├─────────────────────────────────────────────────────────┤
│ Web 层 (dashboard-web) │
│ ├── REST API │
│ ├── JWT 认证 │
│ └── 文件上传 │
├─────────────────────────────────────────────────────────┤
│ 服务层 (dashboard-service) │
│ ├── 图表服务 (Chart Service) │
│ ├── 页面服务 (Page Service) │
│ ├── 数据集服务 (DataSet Service) │
│ └── 数据源服务 (DataSource Service) │
├─────────────────────────────────────────────────────────┤
│ 数据访问层 (dashboard-dao) │
│ └── MyBatis Mapper │
├─────────────────────────────────────────────────────────┤
│ 数据存储 │
│ ├── PostgreSQL/MySQL │
│ ├── Redis (缓存) │
│ └── Ehcache (本地缓存) │
└─────────────────────────────────────────────────────────┘
项目结构
前端 (ClientCode):
ClientCode/
├── app/
│ ├── core/ # 核心框架
│ │ ├── base.js # 控件基类
│ │ ├── loader.js # 插件加载器
│ │ ├── property.js # 属性系统
│ │ └── layout-manager.js # 布局管理器
│ ├── plugins/ # 插件系统
│ │ ├── controls/ # 可视化控件
│ │ │ ├── echarts/ # ECharts 系列
│ │ │ ├── common/ # 通用控件
│ │ │ ├── project/ # 项目定制
│ │ │ └── demo/ # 演示控件
│ │ ├── config.json # 控件注册表
│ │ └── require.js # RequireJS 配置
│ ├── modules/ # AngularJS 模块
│ │ ├── editor/ # 设计器模块
│ │ ├── player/ # 播放器模块
│ │ ├── dataset/ # 数据集模块
│ │ └── schema/ # 数据模式模块
│ ├── layout/ # 布局系统
│ ├── views/ # 共享模板
│ └── main.js # 应用入口
├── libs/ # 第三方库
└── doc/ # GitBook 文档
后端 (DubboService):
DubboService/
├── dashboard-parent/ # 父 POM
├── dashboard-api/ # 服务接口定义
├── dashboard-dao/ # 数据访问层
├── dashboard-service/ # Dubbo 服务提供者
└── dashboard-web/ # Web 服务消费者
前端:可扩展的插件系统
插件架构核心
平台的核心创新在于声明式插件系统。新增图表控件只需三个步骤:
- 在
plugins/controls/下创建控件目录 - 编写
manifest.json描述控件元数据 - 在
config.json中添加一行注册配置
控件基类设计
所有控件继承自统一的基类,定义了标准生命周期:
var Chart = Base.extend({
constructor: function (layout) { },
// 初始化:控件挂载到 DOM
init: function (element) {
// 创建图表实例
},
// 加载示例数据(设计器预览)
example: function () {
this.setData(this.getMockData());
},
// 设置图表数据
setData: function (data) {
// 处理并渲染数据
},
// 设置配置项
setOption: function (option) {
// 更新图表配置
},
// 获取当前配置
getOption: function () {
// 返回配置对象
},
// 设置主题
setTheme: function (theme) {
// 切换视觉主题
},
// 响应式调整
resize: function () {
// 窗口大小变化时重绘
},
// 显示/隐藏加载状态
showLoading: function () { },
hideLoading: function () { },
// 清理资源
dispose: function () {
// 销毁图表实例
},
// 获取 DOM 元素和图表实例
getElement: function () { },
getChart: function () { }
});
插件清单文件
manifest.json - 控件的”身份证”:
{
"uid": "ebar",
"name": "柱状/折线/面积图",
"icon": "images/icon.png",
"version": "0.1.0",
"description": "柱状、折线、面积、散点图四合一",
"bootstrap": "ebar.js",
"editorJs": "editor/editor.js",
"editorTpl": "editor/editor.tpl.html",
"editorCss": "editor/editor.css"
}
控件实现示例
// ebar.js - ECharts 柱状图控件
define(['app/core/base'], function (Base) {
return Base.extend({
init: function (element) {
this.element = element;
this.chart = echarts.init(element[0]);
},
setData: function (data) {
var option = this.buildOption(data);
this.chart.setOption(option);
},
setOption: function (option) {
this.chart.setOption(option);
},
resize: function () {
this.chart.resize();
},
dispose: function () {
this.chart.dispose();
}
});
});
属性系统 - 自动生成属性编辑器
平台最强大的特性是属性元数据系统,通过声明式配置自动生成属性编辑器:
// echartsMeta.js - 属性定义
var property = {
option: [
{
group: '标题',
id: '34ed2334-8d25-4e24-bfc8-59a1ed4ee141',
name: '主标题',
type: 'text',
value: '',
default: '',
link: 'title.text', // 映射到 ECharts 配置路径
tooltip: '主标题文本,支持使用 \\n 换行'
},
{
group: '系列',
id: 'series-type',
name: '图表类型',
type: 'select',
value: 'bar',
options: [
{ value: 'bar', label: '柱状图' },
{ value: 'line', label: '折线图' },
{ value: 'area', label: '面积图' }
],
link: 'series[0].type'
},
{
group: '生命周期',
id: '___Control_Chart_Before_Init_Event',
name: '初始化前执行脚本',
type: 'script', // 支持自定义 JavaScript!
value: '',
link: '__beforeInitScript'
}
]
};
属性类型支持:
text- 文本输入number- 数字输入color- 颜色选择器select- 下拉选择checked- 布尔值colors- 颜色数组seriesBinding- 数据绑定script- JavaScript 代码执行textarea- 多行文本
数据绑定系统
控件通过 seriesBinding 与后端数据集关联:
{
group: '数据绑定',
id: 'series-binding',
name: '序列绑定',
type: 'seriesBinding',
dataset: '', // 数据集编码
dimension: '', // 维度列(X 轴)
measure: '', // 度量列(Y 轴)
tooltip: '配置与数据集的绑定关系'
}
数据流向:
┌─────────────┐ HTTP ┌──────────────┐ Dubbo ┌──────────┐
│ 前端控件 │ ────────► │ Web Controller │ ────────► │ Service │
│ │ │ │ │ │
│ (setData) │ ◄──────── │ JSON Response │ ◄──────── │ MyBatis │
└─────────────┘ └───────────────┘ └────┬─────┘
│
▼
┌──────────┐
│ Database │
└──────────┘
插件注册表
config.json - 所有控件的中央注册表:
{
"controls": [
{
"type": "ECharts 系列",
"controls": [
{ "name": "ebar", "path": "controls/echarts/ebar" },
{ "name": "epie", "path": "controls/echarts/epie" },
{ "name": "egauge", "path": "controls/echarts/egauge" },
{ "name": "emap", "path": "controls/echarts/emap" }
]
},
{
"type": "通用控件",
"controls": [
{ "name": "simplelabel", "path": "controls/common/simplelabel" },
{ "name": "counter", "path": "controls/common/counter" },
{ "name": "datatable", "path": "controls/common/datatable" }
]
},
{
"type": "演示控件",
"controls": [
{ "name": "wordcloud", "path": "controls/demo/wordcloud" },
{ "name": "3dscatter", "path": "controls/demo/3dscatter" }
]
}
]
}
动态加载机制
// loader.js - 插件动态加载
function loadPluginModule(name) {
// 从 LocalStorage 缓存读取清单
var manifests = getCachedManifests();
// 查找目标插件
var module = _.find(manifests, function (manifest) {
return manifest.uid === name;
});
// RequireJS 按需加载
require([module.bootstrap], function (bootstrap) {
// 返回控件类和配置
deferred.resolve(bootstrap, module);
});
}
RequireJS 模块管理系统
RequireJS 是整个前端项目的模块加载核心,负责管理所有第三方库和业务模块的依赖关系。
RequireJS 配置
require.js - 主配置文件:
require.config({
baseUrl: '.', // 基础路径
urlArgs: 'v=' + version, // 版本号防缓存
// 第三方库路径映射
paths: {
// 核心库
'jquery': 'libs/jquery/jquery-2.2.4.min',
'bootstrap': 'libs/bootstrap/bootstrap-3.3.7.min',
// AngularJS 系列
'angular': 'libs/angular/angular-1.5.11.min',
'angular-animate': 'libs/angular/angular-animate-1.5.11.min',
'angular-ui-router': 'libs/angular/angular-ui-router-0.3.2.min',
// ECharts 可视化
'echarts': 'libs/echarts/echarts-3.4.0.min',
'echarts-gl': 'libs/echarts/echarts-gl-1.0.0.min',
// 工具库
'underscore': 'libs/underscore/underscore-1.8.3.min',
'moment': 'libs/moment/moment-2.18.1.min',
// 其他组件
'jquery-ui': 'libs/jqueryui/jquery-ui-1.12.1.min',
'bxslider': 'libs/bxslider/jquery.bxslider.min'
},
// Shim 配置:非 AMD 模块适配
shim: {
'jquery': {
exports: '$'
},
'bootstrap': {
deps: ['jquery'],
exports: 'jQuery'
},
'angular': {
exports: 'angular',
deps: ['jquery']
},
'angular-animate': {
deps: ['angular']
},
'angular-ui-router': {
deps: ['angular']
},
'echarts': {
exports: 'echarts'
},
'underscore': {
exports: '_'
},
'jquery-ui': {
deps: ['jquery']
},
'bxslider': {
deps: ['jquery']
}
}
});
应用启动流程
bootstrap.js - 顺序加载引导:
// 关键脚本必须按顺序加载
var scripts = [
'app/core/assign.js', // ES6 Object.assign polyfill
'app/core/require.js', // 自定义 Require 扩展
'app/plugins/require.js' // 插件配置加载
];
loadScriptsInOrder(scripts).then(function() {
// 所有依赖加载完成后,启动 RequireJS
loadJs({
'src': 'libs/requirejs/require.js',
'data-main': 'require.js' // 入口配置文件
});
});
模块定义模式
AMD 模块定义:
// 方式一:简化 CommonJS 风格
define(['jquery', 'underscore', './utils'], function ($, _, utils) {
// 模块代码
return {
init: function () { }
};
});
// 方式二:依赖数组 + 工厂函数
define(['app/core/base'], function (Base) {
return Base.extend({
init: function (element) {
// 控件初始化
}
});
});
// 方式三:命名模块(插件推荐)
define('controls/echarts/ebar', [
'echarts',
'app/core/base'
], function (echarts, Base) {
// 控件实现
});
AngularJS 与 RequireJS 集成
模块懒加载 - 使用 ocLazyLoad:
// router.js - 路由配置
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('editor', {
url: '/editor',
lazyModule: 'ad.editor', // 目标模块名
lazyFiles: [ // 需要加载的文件
'app/modules/editor/define',
'app/modules/editor/controllers/editor',
'app/modules/editor/directives/sidebar',
'app/modules/editor/services/pageService'
],
lazyTemplateUrl: 'app/modules/editor/views/editor.tpl.html',
resolve: {
// 通过 ocLazyLoad 动态加载
load: ['$ocLazyLoad', '$q', function ($ocLazyLoad, $q) {
var deferred = $q.defer();
require(this.lazyFiles, function () {
deferred.resolve();
});
return deferred.promise;
}]
}
});
}]);
依赖管理策略
libs/ 目录结构:
libs/
├── jquery/
│ └── jquery-2.2.4.min.js
├── bootstrap/
│ └── bootstrap-3.3.7.min.js
├── angular/
│ ├── angular-1.5.11.min.js
│ ├── angular-animate-1.5.11.min.js
│ └── angular-ui-router-0.3.2.min.js
├── echarts/
│ ├── echarts-3.4.0.min.js
│ └── echarts-gl-1.0.0.min.js
├── requirejs/
│ └── require.js
└── underscore/
└── underscore-1.8.3.min.js
Bower 管理依赖 (bower.json):
{
"name": "dashboard-client",
"dependencies": {
"jquery": "~2.2.4",
"bootstrap": "~3.3.7",
"angular": "~1.5.11",
"angular-animate": "~1.5.11",
"angular-ui-router": "~0.3.2",
"underscore": "~1.8.3",
"requirejs": "~2.3.5",
"echarts": "~3.4.0",
"moment": "~2.18.1"
}
}
插件按需加载
插件配置加载 (plugins/require.js):
// 加载所有控件的 manifest.json
require(['plugins/config.json'], function (config) {
config.controls.forEach(function (group) {
group.controls.forEach(function (control) {
// 动态构建插件路径
var path = 'plugins/' + control.path + '/manifest.json';
require([path], function (manifest) {
// 缓存到 LocalStorage
cacheManifest(manifest);
});
});
});
});
循环依赖处理
延迟初始化模式:
// 避免 A 依赖 B,B 又依赖 A 的情况
define(['jquery', 'app/core/manager'], function ($, manager) {
var instance;
function getInstance() {
if (!instance) {
instance = {
init: function () {
// 延迟获取依赖
var dep = manager.getDependency();
}
};
}
return instance;
}
return getInstance();
});
模块缓存清理
开发环境热更新:
// 开发模式下禁用缓存
if (DEBUG_MODE) {
require.config({
urlArgs: 't=' + new Date().getTime()
});
// 清除指定模块缓存
function invalidateModule(moduleId) {
require.undef(moduleId);
}
}
RequireJS 管理优势
| 特性 | 实现方式 |
|---|---|
| 按需加载 | 控件仅在首次使用时加载 |
| 依赖管理 | 自动解析模块依赖关系 |
| 版本控制 | urlArgs 统一版本号管理 |
| 缓存策略 | LocalStorage 缓存 manifest |
| 循环依赖 | 延迟初始化模式解决 |
| Shim 适配 | 非模块库兼容处理 |
后端:Dubbo 微服务架构
服务提供者配置
// dashboard-service - Dubbo 服务提供者
@Service(interfaceClass = AdChartService.class)
@CacheConfig(cacheNames = "chart")
public class AdChartServiceImpl implements AdChartService {
@Autowired
private AdChartMapper adChartMapper;
@Cacheable(key = "'chartId:' + #chartId")
public AdChart selectByPrimaryKey(Integer chartId) {
return adChartMapper.selectByPrimaryKey(chartId);
}
@Cacheable(key = "'allCharts'")
public List<AdChart> selectAdCharts() {
return adChartMapper.selectAdCharts();
}
}
application.properties:
server.port=8787
# Dubbo 提供者配置
spring.dubbo.application.name=dashboard-service
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20880
spring.dubbo.registry.protocol=zookeeper
spring.dubbo.registry.address=127.0.0.1:2181
spring.dubbo.base-package=org.dashboard.service.impl
spring.dubbo.provider.timeout=60000
服务消费者配置
// dashboard-web - REST API 层
@RestController
@RequestMapping("/chart/chart")
public class AdChartController {
@Reference // Dubbo 远程服务注入
private AdChartService adChartService;
@RequestMapping("/get")
public JsonResponse getAllCharts() {
List<AdChart> charts = adChartService.selectAdCharts();
return JsonResponse.success(charts);
}
@RequestMapping("/search")
public JsonResponse searchCharts(
@RequestParam int page,
@RequestParam int rows) {
PageInfo<AdChart> pageInfo =
adChartService.selectAdChartsByPage(page, rows);
return JsonResponse.success(pageInfo);
}
}
application.properties:
server.port=8888
# Dubbo 消费者配置
spring.dubbo.application.name=dashboard-web
spring.dubbo.registry.protocol=zookeeper
spring.dubbo.registry.address=127.0.0.1:2181
spring.dubbo.consumer.timeout=60000
spring.dubbo.consumer.check=false
服务分层架构
┌─────────────────────────────────────────┐
│ dashboard-web :8888 │
│ ┌──────────────┬──────────────┐ │
│ │ Controllers │ JWT Filter │ │
│ └──────┬───────┴──────────────┘ │
│ │ @Reference │
│ ▼ │
│ ┌──────────────────────────────┐ │
│ │ Dubbo Service Reference │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────────┘
│ Dubbo RPC (Hessian)
┌─────────────────────────────────────────┐
│ dashboard-service :8787 │
│ ┌──────────────────────────────┐ │
│ │ @Service Providers │ │
│ │ ├── ChartServiceImpl │ │
│ │ ├── PageServiceImpl │ │
│ │ ├── DataSetServiceImpl │ │
│ │ └── DataSourceServiceImpl │ │
│ └──────┬────────────────────────┘ │
│ │ @Autowired │
│ ▼ │
│ ┌──────────────────────────────┐ │
│ │ MyBatis Mapper │ │
│ └──────┬───────────────────────┘ │
└─────────┼───────────────────────────────┘
│ SQL
┌─────────▼──────────┐
│ PostgreSQL │
│ ┌────────────┐ │
│ │ ad_chart │ │
│ │ ad_page │ │
│ │ ad_dataset │ │
│ └────────────┘ │
└────────────────────┘
Zookeeper :2181
┌───────────────┐
│ Service Registry│
└───────────────┘
数据模型
// 图表配置存储
public class AdChart {
private Integer chartId; // 图表 ID
private String chartName; // 图表名称
private String chartTypeId; // 控件类型 UID
private String chartConfig; // JSON 配置
private Integer folderId; // 所属文件夹
private Date createTime; // 创建时间
private Date updateTime; // 更新时间
}
// chartConfig 存储内容
{
"Option": { /* ECharts 配置对象 */ },
"Binding": [
{
"DataSetCode": "ds001",
"Dimensions": [
{ "Code": "col1", "Column": "地区" }
],
"Measures": [
{ "Code": "col2", "Column": "销售额" }
]
}
],
"Extend": {
"option": [ /* 属性编辑器配置 */ ]
}
}
缓存策略
@CacheConfig(cacheNames = "chart")
public class AdChartServiceImpl implements AdChartService {
// Redis 缓存 + Ehcache 本地缓存
@Cacheable(key = "'chartId:' + #chartId")
public AdChart selectByPrimaryKey(Integer chartId) {
return adChartMapper.selectByPrimaryKey(chartId);
}
@CacheEvict(key = "'chartId:' + #chart.chartId")
public int updateByPrimaryKey(AdChart chart) {
return adChartMapper.updateByPrimaryKey(chart);
}
}
关键技术特性
前端技术栈
| 技术 | 版本 | 用途 |
|---|---|---|
| AngularJS | 1.5.x | SPA 框架 |
| RequireJS | 2.3.x | AMD 模块加载 |
| UI-Router | 0.2.x | 路由管理 |
| Bootstrap | 3.x | UI 框架 |
| ECharts | 3.x | 数据可视化 |
| jQuery | 2.x | DOM 操作 |
| OC-LazyLoad | 1.x | 懒加载模块 |
后端技术栈
| 技术 | 版本 | 用途 |
|---|---|---|
| Spring Boot | 1.5.10 | 应用框架 |
| Dubbo | 2.5.10 | RPC 框架 |
| Zookeeper | 3.4.10 | 服务注册中心 |
| MyBatis | 3.x | ORM 框架 |
| Redis | 3.x | 分布式缓存 |
| Ehcache | 2.x | 本地缓存 |
| PostgreSQL | 9.x | 关系型数据库 |
核心特性
| 特性 | 实现方案 |
|---|---|
| 可扩展控件系统 | 声明式插件 + 属性元数据 |
| 按需加载 | RequireJS + OC-LazyLoad |
| 数据绑定 | DataSet + SeriesBinding |
| 属性编辑器 | 自动生成 + 生命周期脚本 |
| 微服务架构 | Dubbo + Zookeeper |
| 缓存策略 | Redis + Ehcache 二级缓存 |
| 认证授权 | JWT Token |
| 离线支持 | IndexedDB + LocalStorage |
平台优势
1. 真正的插件化架构
新增图表控件无需修改核心代码:
- 创建控件目录和 manifest.json
- 实现基类方法
- 在 config.json 注册
2. 属性驱动的编辑器
通过属性元数据自动生成配置界面:
- 支持多种数据类型
- 支持自定义脚本注入
- 支持生命周期钩子
3. 微服务可扩展性
Dubbo 架构带来的优势:
- 服务独立部署
- 水平扩展能力
- 服务注册发现
- 负载均衡
4. 完善的文档体系
GitBook 格式的插件开发文档:
plugin-intro.html- 插件开发入门plugin-conf.html- 配置文件说明plugin-review.html- 开发规范explain-*.html- 技术细节
技术背景 (2015)
这个项目诞生于 2015 年,采用了当时主流的技术方案:
前端领域:
- AngularJS 1.x 仍是 SPA 开发主流
- React 刚刚兴起,尚未普及
- Vue.js 还未发布
- Webpack 尚未成为标准
- ES6 modules 尚未得到浏览器支持
后端领域:
- Spring Boot 1.x 是微服务首选
- Dubbo 是阿里开源的成熟 RPC 框架
- Spring Cloud 尚未成熟
- Kubernetes 刚刚开源
在那个时代,这个架构选择是务实且先进的。
总结
AmazingDashboard 是一个设计精良的数据可视化平台,其核心价值在于:
- 可扩展的插件系统 - 通过声明式配置即可扩展
- 属性元数据驱动 - 自动生成配置界面
- 微服务架构支撑 - Dubbo 提供可靠的服务治理
- 完善的文档体系 - 降低插件开发门槛
这套架构在 2015 年是一个成熟且具有前瞻性的解决方案,为数据可视化领域提供了一个可参考的实践范例。