qianlishi 3 years ago
commit b31e629c6b

@ -1,3 +1,7 @@
## 简介
&emsp; &emsp; AJ-Report是一个完全开源的BI平台酷炫大屏展示能随时随地掌控业务动态让每个决策都有数据支撑。<br>
&emsp; &emsp; 多数据源支持内置mysql、elasticsearch、kudu驱动支持自定义数据集省去数据接口开发支持17种大屏组件不会开发照着设计稿也可以制作大屏。<br>
&emsp; &emsp; 三步轻松完成大屏设计:配置数据源---->写SQL配置数据集---->拖拽配置大屏---->保存发布。欢迎体验。
## 在线体验 ## 在线体验
#### &emsp; 电脑在线体验: [https://report.anji-plus.com/index.html](https://report.anji-plus.com/index.html "链接") &emsp;体验账号guest 密码guest #### &emsp; 电脑在线体验: [https://report.anji-plus.com/index.html](https://report.anji-plus.com/index.html "链接") &emsp;体验账号guest 密码guest
#### &emsp; 在线文档: [https://report.anji-plus.com/report-doc/](https://report.anji-plus.com/report-doc/ "doc")<br> #### &emsp; 在线文档: [https://report.anji-plus.com/report-doc/](https://report.anji-plus.com/report-doc/ "doc")<br>
@ -42,6 +46,22 @@
├── README.md ├── README.md
``` ```
## 核心技术
### 后端
- [Spring Boot2.3.5.RELEASE](https://spring.io/projects/spring-boot/): Spring Boot是一款开箱即用框架让我们的Spring应用变的更轻量化、更快的入门。 在主程序执行main函数就可以运行。你也可以打包你的应用为jar并通过使用java -jar来运行你的Web应用
- [Mybatis-plus3.3.2](https://mp.baomidou.com/): MyBatis-plus简称 MP是一个 MyBatis (opens new window) 的增强工具。
- [flyway5.2.1](https://flywaydb.org/): 主要用于在你的应用版本不断升级的同时,升级你的数据库结构和里面的数据
### 前端
- [npm](https://www.npmjs.com/)node.js的包管理工具用于统一管理我们前端项目中需要用到的包、插件、工具、命令等便于开发和维护。
- [webpack](https://webpack.docschina.org/):用于现代 JavaScript 应用程序的_静态模块打包工具
- [ES6](https://es6.ruanyifeng.com/)Javascript的新版本ECMAScript6的简称。利用ES6我们可以简化我们的JS代码同时利用其提供的强大功能来快速实现JS逻辑。
- [vue-cli](https://cli.vuejs.org/)Vue的脚手架工具用于自动生成Vue项目的目录及文件。
- [vue-router](https://router.vuejs.org/) Vue提供的前端路由工具利用其我们实现页面的路由控制局部刷新及按需加载构建单页应用实现前后端分离。
- [element-ui](https://element.eleme.cn/#/zh-CN)基于MVVM框架Vue开源出来的一套前端ui组件。
- [avue](https://www.avuejs.com/): 用该组件包裹后可以变成拖拽组件,采用相对于父类绝对定位;用键盘的上下左右也可以控制移动
- [vue-echarts](https://www.npmjs.com/package/vue-echarts/): vue-echarts是封装后的vue插件,基于 ECharts v4.0.1+ 开发
- [vue-superslide](https://www.npmjs.com/package/vue-super-slider/): Vue-SuperSlide(Github) 是 SuperSlide 的 Vue 封装版本
- [vuedraggable](https://github.com/SortableJS/Vue.Draggable/): 是一款基于Sortable.js实现的vue拖拽插件。
## 近期计划 ## 近期计划
- 完善地图插件 - 完善地图插件

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 16 KiB

@ -21,7 +21,7 @@
</modules> </modules>
<properties> <properties>
<gaea.version>2.0.0.RELEASE</gaea.version> <gaea.version>2.0.1.RELEASE</gaea.version>
<gaea.security.version>1.0.0-SNAPSHOT</gaea.security.version> <gaea.security.version>1.0.0-SNAPSHOT</gaea.security.version>
<gaea.export.version>1.0.0.RELEASE</gaea.export.version> <gaea.export.version>1.0.0.RELEASE</gaea.export.version>
<gaea.generator.version>1.0.0-SNAPSHOT</gaea.generator.version> <gaea.generator.version>1.0.0-SNAPSHOT</gaea.generator.version>

@ -93,6 +93,12 @@
<version>4.4.5</version> <version>4.4.5</version>
</dependency> </dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
<resources> <resources>

@ -0,0 +1,54 @@
package com.anjiplus.template.gaea.business.config;
import com.zaxxer.hikari.HikariDataSource;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.flyway.FlywayProperties;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
/**
* Created by raodeming on 2021/7/5.
*/
@Slf4j
@Component
@AllArgsConstructor
public class DatabaseInitializer {
private final FlywayProperties flywayProperties;
private final DataSourceProperties dataSourceProperties;
@PostConstruct
public void init() throws SQLException {
log.info("DatabaseInitializer uses flyway init-sqls to initiate database");
String url = dataSourceProperties.getUrl();
// jdbc url最后一个 '/' 用于分割具体 schema?参数
int lastSplitIndex = url.lastIndexOf('?');
// 获取spring.datasource.url具体数据库schema前的jdbc url
String addressUrl = url.substring(0, lastSplitIndex);
String addresslast = url.substring(lastSplitIndex);
addressUrl = addressUrl.substring(0, addressUrl.lastIndexOf("/"));
// 直连数据库地址:jdbc:mysql://yourIp:port
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(addressUrl.concat(addresslast));
dataSource.setUsername(dataSourceProperties.getUsername());
dataSource.setPassword(dataSourceProperties.getPassword());
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
for (String sql : flywayProperties.getInitSqls()) {
// 通过flyway的init-sqls配置进行建库与数据库配置
// executeUpdate:执行给定的SQL语句该语句可以是INSERTUPDATE或DELETE语句或不返回任何内容的SQL语句例如SQL DDL语句。
statement.executeUpdate(sql);
}
statement.close();
connection.close();
dataSource.close();
log.info("DatabaseInitializer initialize completed");
}
}

@ -37,7 +37,6 @@ public class TokenFilter implements Filter {
String uri = request.getRequestURI(); String uri = request.getRequestURI();
if (!uri.startsWith("/login") if (!uri.startsWith("/login")
&& !uri.startsWith("/favicon.ico")
&& !uri.startsWith("/static") && !uri.startsWith("/static")
&& !uri.startsWith("/file/download/") && !uri.startsWith("/file/download/")
&& !uri.contains("index.html")) { && !uri.contains("index.html")) {

@ -1,5 +1,6 @@
package com.anjiplus.template.gaea.business.modules.dataSource.pool.util; package com.anjiplus.template.gaea.business.modules.dataSource.pool.util;
import com.alibaba.druid.pool.DruidDataSource;
import com.anjiplus.template.gaea.business.modules.dataSource.controller.dto.DataSourceDto; import com.anjiplus.template.gaea.business.modules.dataSource.controller.dto.DataSourceDto;
import com.anjiplus.template.gaea.business.modules.dataSource.pool.datasource.PooledDataSource; import com.anjiplus.template.gaea.business.modules.dataSource.pool.datasource.PooledDataSource;
import com.anjiplus.template.gaea.business.modules.dataSource.pool.datasource.UnPooledDataSource; import com.anjiplus.template.gaea.business.modules.dataSource.pool.datasource.UnPooledDataSource;
@ -9,6 +10,7 @@ import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@ -18,33 +20,34 @@ import java.util.concurrent.locks.ReentrantLock;
@Slf4j @Slf4j
public class JdbcUtil { public class JdbcUtil {
private static Lock lock = new ReentrantLock();
private static Lock deleteLock = new ReentrantLock();
//所有数据源的连接池存在map里 //所有数据源的连接池存在map里
static Map<Long, PooledDataSource> map = new HashMap<>(); static Map<Long, DruidDataSource> map = new ConcurrentHashMap<>();
public static PooledDataSource getJdbcConnectionPool(DataSourceDto dataSource) { public static DruidDataSource getJdbcConnectionPool(DataSourceDto dataSource) {
if (map.containsKey(dataSource.getId())) { if (map.containsKey(dataSource.getId())) {
return map.get(dataSource.getId()); return map.get(dataSource.getId());
} else { } else {
lock.lock();
try { try {
log.debug(Thread.currentThread().getName() + "获取锁");
if (!map.containsKey(dataSource.getId())) { if (!map.containsKey(dataSource.getId())) {
PooledDataSource pool = new PooledDataSource(); DruidDataSource pool = new DruidDataSource();
pool.setJdbcUrl(dataSource.getJdbcUrl()); pool.setUrl(dataSource.getJdbcUrl());
pool.setUser(dataSource.getUsername()); pool.setUsername(dataSource.getUsername());
pool.setPassword(dataSource.getPassword()); pool.setPassword(dataSource.getPassword());
pool.setDriverClass(dataSource.getDriverName()); pool.setDriverClassName(dataSource.getDriverName());
pool.init();
//下面都是可选的配置
pool.setInitialSize(10); //初始连接数默认0
pool.setMaxActive(30); //最大连接数默认8
pool.setMinIdle(10); //最小闲置数
pool.setMaxWait(2000); //获取连接的最大等待时间,单位毫秒
pool.setPoolPreparedStatements(true); //缓存PreparedStatement默认false
pool.setMaxOpenPreparedStatements(20); //缓存PreparedStatement的最大数量默认-1不缓存。大于0时会自动开启缓存PreparedStatement所以可以省略上一句代码
map.put(dataSource.getId(), pool); map.put(dataSource.getId(), pool);
log.info("创建连接池成功:{}", dataSource.getJdbcUrl()); log.info("创建连接池成功:{}", dataSource.getJdbcUrl());
} }
return map.get(dataSource.getId()); return map.get(dataSource.getId());
} finally { } finally {
lock.unlock();
} }
} }
} }
@ -54,18 +57,15 @@ public class JdbcUtil {
* @param id * @param id
*/ */
public static void removeJdbcConnectionPool(Long id) { public static void removeJdbcConnectionPool(Long id) {
deleteLock.lock();
try { try {
PooledDataSource pool = map.get(id); DruidDataSource pool = map.get(id);
if (pool != null) { if (pool != null) {
map.remove(id); map.remove(id);
} }
} catch (Exception e) { } catch (Exception e) {
log.error(e.toString()); log.error(e.toString());
} finally { } finally {
deleteLock.unlock();
} }
} }
/** /**
@ -75,7 +75,7 @@ public class JdbcUtil {
* @throws SQLException * @throws SQLException
*/ */
public static Connection getPooledConnection(DataSourceDto dataSource) throws SQLException { public static Connection getPooledConnection(DataSourceDto dataSource) throws SQLException {
PooledDataSource pool = getJdbcConnectionPool(dataSource); DruidDataSource pool = getJdbcConnectionPool(dataSource);
return pool.getConnection(); return pool.getConnection();
} }

@ -3,8 +3,10 @@ package com.anjiplus.template.gaea.business.modules.dataSource.service.impl;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.anji.plus.gaea.constant.BaseOperationEnum;
import com.anji.plus.gaea.constant.Enabled; import com.anji.plus.gaea.constant.Enabled;
import com.anji.plus.gaea.curd.mapper.GaeaBaseMapper; import com.anji.plus.gaea.curd.mapper.GaeaBaseMapper;
import com.anji.plus.gaea.exception.BusinessException;
import com.anji.plus.gaea.exception.BusinessExceptionBuilder; import com.anji.plus.gaea.exception.BusinessExceptionBuilder;
import com.anji.plus.gaea.utils.GaeaAssert; import com.anji.plus.gaea.utils.GaeaAssert;
import com.anjiplus.template.gaea.business.code.ResponseCode; import com.anjiplus.template.gaea.business.code.ResponseCode;
@ -379,5 +381,19 @@ public class DataSourceServiceImpl implements DataSourceService {
dto.setBody(body); dto.setBody(body);
} }
/**
*
*
* @param entity
* @param operationEnum
* @throws BusinessException
*/
@Override
public void processAfterOperation(DataSource entity, BaseOperationEnum operationEnum) throws BusinessException {
switch (operationEnum){
case DELETE:
JdbcUtil.removeJdbcConnectionPool(entity.getId());
break;
}
}
} }

@ -1,25 +1,25 @@
CREATE CREATE
DATABASE IF NOT EXISTS `aj_report` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; DATABASE IF NOT EXISTS `aj_report` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
--
USE -- USE
`aj_report`; -- `aj_report`;
--
CREATE TABLE `flyway_schema_history` -- CREATE TABLE `flyway_schema_history`
( -- (
`installed_rank` int(11) NOT NULL, -- `installed_rank` int(11) NOT NULL,
`version` varchar(50) DEFAULT NULL, -- `version` varchar(50) DEFAULT NULL,
`description` varchar(200) NOT NULL, -- `description` varchar(200) NOT NULL,
`type` varchar(20) NOT NULL, -- `type` varchar(20) NOT NULL,
`script` varchar(1000) NOT NULL, -- `script` varchar(1000) NOT NULL,
`checksum` int(11) DEFAULT NULL, -- `checksum` int(11) DEFAULT NULL,
`installed_by` varchar(100) NOT NULL, -- `installed_by` varchar(100) NOT NULL,
`installed_on` timestamp NOT NULL DEFAULT current_timestamp(), -- `installed_on` timestamp NOT NULL DEFAULT current_timestamp(),
`execution_time` int(11) NOT NULL, -- `execution_time` int(11) NOT NULL,
`success` tinyint(1) NOT NULL, -- `success` tinyint(1) NOT NULL,
PRIMARY KEY (`installed_rank`), -- PRIMARY KEY (`installed_rank`),
KEY `flyway_schema_history_s_idx` (`success`) -- KEY `flyway_schema_history_s_idx` (`success`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
insert into `flyway_schema_history`(`installed_rank`, `version`, `description`, `type`, `script`, `checksum`, -- insert into `flyway_schema_history`(`installed_rank`, `version`, `description`, `type`, `script`, `checksum`,
`installed_by`, `installed_on`, `execution_time`, `success`) -- `installed_by`, `installed_on`, `execution_time`, `success`)
values (1, '1', '<< Flyway Baseline >>', 'BASELINE', '<< Flyway Baseline >>', NULL, 'admin', now(), 0, 1); -- values (1, '1', '<< Flyway Baseline >>', 'BASELINE', '<< Flyway Baseline >>', NULL, 'admin', now(), 0, 1);

File diff suppressed because one or more lines are too long

@ -1,5 +1,6 @@
import miment from 'miment' import miment from 'miment'
import {getData} from '@/api/bigscreen' import {getData} from '@/api/bigscreen'
export default { export default {
data() { data() {
return { return {
@ -10,7 +11,7 @@ export default {
}, },
//日期时间快捷选项 //日期时间快捷选项
datetimeRangePickerOptions:{ datetimeRangePickerOptions: {
shortcuts: [{ shortcuts: [{
text: '今天', text: '今天',
onClick(picker) { onClick(picker) {
@ -18,14 +19,14 @@ export default {
const start = new Date(new Date(new Date().getTime()).setHours(0, 0, 0, 0)); const start = new Date(new Date(new Date().getTime()).setHours(0, 0, 0, 0));
picker.$emit('pick', [start, end]); picker.$emit('pick', [start, end]);
} }
},{ }, {
text: '昨天', text: '昨天',
onClick(picker) { onClick(picker) {
const start=new Date(new Date(new Date().getTime()-24*60*60*1000).setHours(0, 0, 0, 0)); const start = new Date(new Date(new Date().getTime() - 24 * 60 * 60 * 1000).setHours(0, 0, 0, 0));
const end=new Date(new Date(new Date().getTime()-24*60*60*1000).setHours(23, 59, 59, 999)); const end = new Date(new Date(new Date().getTime() - 24 * 60 * 60 * 1000).setHours(23, 59, 59, 999));
picker.$emit('pick', [start, end]); picker.$emit('pick', [start, end]);
} }
},{ }, {
text: '最近一周', text: '最近一周',
onClick(picker) { onClick(picker) {
const end = new Date(); const end = new Date();
@ -51,13 +52,12 @@ export default {
} }
}], }],
// disabledDate(time){ // disabledDate(time){
// return time.getTime() > Date.now() // return time.getTime() > Date.now()
// } // }
}, },
} }
}, },
computed: { computed: {},
},
created() { created() {
}, },
mounted() { mounted() {
@ -66,8 +66,8 @@ export default {
}, },
methods: { methods: {
// 搜索重置搜索页码 // 搜索重置搜索页码
search(){ search() {
this.params.currentPage=1; this.params.currentPage = 1;
this.queryByPage(); this.queryByPage();
}, },
// 把selectInput中的值赋到params查询对象中 // 把selectInput中的值赋到params查询对象中
@ -80,7 +80,7 @@ export default {
// console.warn('params has no field:' + keyname) // console.warn('params has no field:' + keyname)
// return // return
// } // }
if (keyword !== undefined ) { if (keyword !== undefined) {
this.params[keyname] = keyword.trim() this.params[keyname] = keyword.trim()
} }
}, },
@ -90,22 +90,24 @@ export default {
data[k] = null data[k] = null
} }
}, },
handlerInputchange(val){ handlerInputchange(val) {
this.parseParamsBySelectInput(this.selectInput.keyname, val) this.parseParamsBySelectInput(this.selectInput.keyname, val)
}, },
// 查询echarts 数据 // 查询echarts 数据
queryEchartsData(params) { queryEchartsData(params) {
return new Promise(async(resolve) => { return new Promise(async (resolve) => {
const { code, data } = await getData(params); const {code, data} = await getData(params);
if(code != 200) return if (code != 200) return
const analysisData = this.analysisChartsData(params, data); const analysisData = this.analysisChartsData(params, data);
resolve(analysisData) resolve(analysisData)
}) })
}, },
// 解析不同图标的数据 // 解析不同图标的数据
analysisChartsData(params, data) { analysisChartsData(params, data) {
// widget-barchart 柱线图、widget-linechart 折线图、 widget-barlinechart 柱线图、widget-piechart 饼图、widget-hollow-piechart 空心饼图 // widget-barchart 柱线图、widget-linechart 折线图、 widget-barlinechart 柱线图
// widget-funnel 漏斗图 widget-gauge 仪表盘 // widget-piechart 饼图、widget-hollow-piechart 空心饼图、widget-funnel 漏斗图
// widget-gauge 仪表盘
// widget-text 文本框
const chartType = params.chartType const chartType = params.chartType
if ( if (
chartType == "widget-barchart" || chartType == "widget-barchart" ||
@ -121,7 +123,10 @@ export default {
return this.piechartFn(params.chartProperties, data); return this.piechartFn(params.chartProperties, data);
} else if (chartType == "widget-gauge") { } else if (chartType == "widget-gauge") {
return this.gaugeFn(params.chartProperties, data); return this.gaugeFn(params.chartProperties, data);
} else if (chartType == "widget-text") {
return this.widgettext(params.chartProperties, data)
} else { } else {
} }
}, },
// 柱状图、折线图、折柱图 // 柱状图、折线图、折柱图
@ -190,7 +195,23 @@ export default {
} }
// console.log(ananysicData, '结果数据') // console.log(ananysicData, '结果数据')
return ananysicData[0]; return ananysicData[0];
} },
widgettext(chartProperties, data) {
const ananysicData = [];
for (let i = 0; i < data.length; i++) {
const obj = {};
for (const key in chartProperties) {
const value = chartProperties[key];
if (value === "name") {
//obj["name"] = data[i][key];
} else {
obj["value"] = data[i][key];
}
}
ananysicData.push(obj);
}
return ananysicData;
},
}, },
watch: { watch: {
'selectInput.keyname'(newVal, oldVal) { 'selectInput.keyname'(newVal, oldVal) {
@ -200,7 +221,7 @@ export default {
this.parseParamsBySelectInput(newVal, this.selectInput.keyword) this.parseParamsBySelectInput(newVal, this.selectInput.keyword)
}, },
'selectInput.keyword'(newVal, oldVal) { 'selectInput.keyword'(newVal, oldVal) {
if (!this.selectInput.keyname) return if (!this.selectInput.keyname) return
this.parseParamsBySelectInput(this.selectInput.keyname, newVal) this.parseParamsBySelectInput(this.selectInput.keyname, newVal)
} }
// 'selectInput.keyword'(newVal, oldVal) { // 'selectInput.keyword'(newVal, oldVal) {

@ -184,6 +184,17 @@ const widgetTools = [
relactiveDomValue: 'staticData', relactiveDomValue: 'staticData',
value: '文本框', value: '文本框',
}, },
{
type: 'dycustComponents',
label: '',
name: 'dynamicData',
required: false,
placeholder: 'px',
relactiveDom: 'dataType',
relactiveDomValue: 'dynamicData',
chartType: 'widget-text',
value: '',
},
], ],
// 坐标 // 坐标

@ -179,9 +179,6 @@ export default {
fontSize: optionsCollapse.fontSizeY fontSize: optionsCollapse.fontSizeY
} }
}, },
splitLine: {
show: false
},
axisLine: { axisLine: {
show: true, show: true,
lineStyle: { lineStyle: {

@ -18,7 +18,8 @@ export default {
}, },
data() { data() {
return { return {
options: {} options: {},
optionsData: {}
}; };
}, },
computed: { computed: {
@ -47,14 +48,49 @@ export default {
value: { value: {
handler(val) { handler(val) {
this.options = val; this.options = val;
this.optionsData = val.data
this.setOptionsData()
}, },
deep: true deep: true
} }
}, },
mounted() { mounted() {
this.options = this.value; this.options = this.value;
this.optionsData = this.value.data
this.setOptionsData()
}, },
methods: {} methods: {
//
setOptionsData() {
const optionsData = this.optionsData; // or
optionsData.dataType == "staticData"
? this.staticDataFn(optionsData.staticData)
: this.dynamicDataFn(
optionsData.dynamicData,
optionsData.refreshTime
);
},
staticDataFn(val) {
},
dynamicDataFn(val, refreshTime) {
if (!val) return;
if (this.ispreview) {
this.getEchartData(val);
this.flagInter = setInterval(() => {
this.getEchartData(val);
}, refreshTime);
} else {
this.getEchartData(val);
}
},
getEchartData(val) {
const data = this.queryEchartsData(val);
data.then(res => {
this.styleColor.text = res[0].value
this.$forceUpdate();
});
},
}
}; };
</script> </script>

Loading…
Cancel
Save