Compare commits

..

4 Commits

8 changed files with 569 additions and 10 deletions

View File

@ -5,6 +5,7 @@ import cn.hutool.core.date.LocalDateTimeUtil;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
@ -157,4 +158,26 @@ public class DateUtils {
localFormat.setTimeZone(TimeZone.getDefault()); // 设置为系统默认时区
return localFormat.format(utcDate); // 转换为系统时区时间
}
public static LocalDateTime tranUTCtoLocalDateTime(String utcTimeString){
Instant instant = Instant.parse(utcTimeString);
// 获取本地时区
ZoneId localZoneId = ZoneId.systemDefault();
// 将Instant对象转换为LocalDateTime
return LocalDateTime.ofInstant(instant, localZoneId);
}
/**
* 将utc时间转北京时间
* @param date utc时间
* @return 北京时间
*/
public static String utcToCst(String date) {
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
LocalDateTime localDateTime = LocalDateTime.parse(date, formatter);
localDateTime.plusHours(8);
DateTimeFormatter returnFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return localDateTime.format(returnFormatter);
}
}

View File

@ -3,6 +3,7 @@ package com.inspur.module.data.controller.admin.query;
import com.inspur.framework.apilog.core.annotation.ApiAccessLog;
import com.inspur.framework.common.pojo.CommonResult;
import com.inspur.framework.common.pojo.PageParam;
import com.inspur.framework.common.util.json.JsonUtils;
import com.inspur.framework.common.util.object.BeanUtils;
import com.inspur.framework.excel.core.util.ExcelUtils;
import com.inspur.module.data.controller.admin.query.vo.*;
@ -11,6 +12,7 @@ import com.inspur.module.system.controller.admin.alarm.vo.AlarmDataPageReqVO;
import com.inspur.module.system.controller.admin.alarm.vo.AlarmDataRespVO;
import com.inspur.module.system.dal.dataobject.alarm.AlarmDataDO;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@ -40,6 +42,9 @@ public class DataQueryController {
@Resource
private IDataQueryService dataQueryService;
@Resource
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/list")
@Operation(summary = "获取/查询网关数据列表")
@PreAuthorize("@ss.hasPermission('data:query:list')")
@ -54,6 +59,24 @@ public class DataQueryController {
return success(resMap);
}
@GetMapping("/getFieldList")
@Operation(summary = "获取/查询网关数据列表")
@PreAuthorize("@ss.hasPermission('data:query:list')")
public CommonResult<Map<String, Object>> getFieldList(String equipId,String nameKey,String tableName, String startTime, String endTime){
Map<String,Object> resMap = new HashMap<>();
Map<String,String> mappingMap = JsonUtils.parseObject(stringRedisTemplate.opsForValue().get("alarmKey2MeasurementMapping"),Map.class);
if (!mappingMap.containsKey(nameKey)){
return CommonResult.error(800,"找不到给参数匹配名对应的表名!");
}
try {
resMap = dataQueryService.queryDataListByColumnNameandDate(equipId,mappingMap.get(nameKey),nameKey,startTime,endTime);
} catch (ParseException e) {
e.printStackTrace();
return CommonResult.error(800,"时间格式错误");
}
return success(resMap);
}
@GetMapping("/export-current-excel")
@Operation(summary = "导出机床电流传感器参数 Excel")
@PreAuthorize("@ss.hasPermission('data:query:export')")

View File

@ -7,6 +7,10 @@ import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.text.ParseException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -44,6 +48,46 @@ public class DataQueryService implements IDataQueryService{
return resMap;
}
/**
* 根据起止时间和字段名称查询数据
*/
public Map<String, Object> queryDataListByColumnNameandDate(String equipId, String tableName, String columnName, String startTime, String endTime) throws ParseException {
LocalDateTime stime = DateUtils.tranUTCtoLocalDateTime(startTime);
LocalDateTime etime = DateUtils.tranUTCtoLocalDateTime(endTime);
long intervalHours = ChronoUnit.HOURS.between(stime, etime);
List<Map<String, Object>> list = new ArrayList<>();
if(intervalHours <= 6){//6小时内全查
list = selectDataByColumnNameandDate(null,tableName, columnName, startTime, endTime);
}else if(intervalHours <= 7*24 ){//7天内每30s一个
list = selectLongTimeDataByColumnNameandDate(null,tableName, columnName, startTime, endTime, "30s");
}else if(intervalHours <= 30*24 ){//30天内每2min一个
list = selectLongTimeDataByColumnNameandDate(null,tableName, columnName, startTime, endTime, "2m");
}else if (intervalHours <= 90*24){//90天以上每4h一个
list = selectLongTimeDataByColumnNameandDate(null,tableName, columnName, startTime, endTime, "6m");
}
Map<String, Object> resMap = new HashMap<>();
if (list.size() == 0){
return null;
}
List<Object> columnList = new ArrayList<>();
List<String> timelist = new ArrayList<>();
if(intervalHours > 6){
columnName = "mean";
}
for (Map<String, Object> data : list) {
columnList.add(data.get(columnName));
timelist.add(DateUtils.utcToCst(data.get("time").toString()));
}
resMap.put("y", columnList);
resMap.put("x", timelist);
return resMap;
}
private List<Map<String, Object>> queryDataByTime(String tableName, String equipId, String beginTime, String endTime, Integer pageNum, Integer pageSize) {
StringBuilder sql = new StringBuilder("select * from ")
.append("\"").append(tableName).append("\"");
@ -100,4 +144,67 @@ public class DataQueryService implements IDataQueryService{
}
return influxDBService.countResultProcess(influxDBService.query(sql.toString()));
}
/**
* 根据字段名称和起止时间查询数据
* @param tableName 表名
* @param columnName 字段名称
* @param beginTime 起始时间
* @param endTime 结束时间
* @return 数据
*/
private List<Map<String, Object>> selectDataByColumnNameandDate(String equipId, String tableName, String columnName, String beginTime, String endTime){
StringBuilder sql = new StringBuilder("select time,").append(columnName).append(" from ")
.append("\"").append(tableName).append("\"");//TODO 修改时间
if(equipId != null){
sql.append(" where equip_id = '").append(equipId).append("'");
}
if(beginTime != null){
if(!sql.toString().contains("where")){
sql.append(" where");
}else{
sql.append(" and");
}
sql.append(" time >= '").append(beginTime).append("'");
}
if(endTime != null){
if(!sql.toString().contains("where")){
sql.append(" where");
}else{
sql.append(" and");
}
sql.append(" time <= '").append(endTime).append("'");
}
return influxDBService.queryResultProcess(influxDBService.query(sql.toString()));
}
/**
* 查询长时间数据每分钟取一条展示最长3个月
*/
private List<Map<String, Object>> selectLongTimeDataByColumnNameandDate(String equipId,String tableName, String columnName, String beginTime, String endTime, String interval){
StringBuilder sql = new StringBuilder("select time, mean(").append(columnName).append(") from ")
.append("\"").append(tableName).append("\"");
if(equipId != null){
sql.append(" where equip_id = '").append(equipId).append("'");
}
if(beginTime != null){
if(!sql.toString().contains("where")){
sql.append(" where");
}else{
sql.append(" and");
}
sql.append(" time >= '").append(beginTime).append("'");
}
if(endTime != null){
if(!sql.toString().contains("where")){
sql.append(" where");
}else{
sql.append(" and");
}
sql.append(" time <= '").append(endTime).append("'");
}
sql.append(" group by time(").append(interval).append(") fill(none)");
return influxDBService.queryResultProcess(influxDBService.query(sql.toString()));
}
}

View File

@ -17,4 +17,9 @@ public interface IDataQueryService {
*/
Map<String, Object> selectDataListByPages(String equipId,String tableName,String startTime,String endTime,Integer pageSize,Integer pageNum) throws ParseException;
/**
* 根据起止时间和字段名称查询数据
*/
public Map<String, Object> queryDataListByColumnNameandDate(String equipId, String tableName, String columnName, String startTime, String endTime) throws ParseException;
}

View File

@ -1,5 +1,6 @@
package com.inspur.module.data.service;
import com.inspur.framework.common.util.json.JsonUtils;
import com.inspur.module.data.config.InfluxDBConfig;
import org.influxdb.InfluxDB;
import org.influxdb.InfluxDBFactory;
@ -10,9 +11,11 @@ import org.influxdb.dto.QueryResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -30,6 +33,9 @@ public class InfluxDBService {
@Autowired
private InfluxDBConfig influxDBConfig;
@Resource
private StringRedisTemplate stringRedisTemplate;
//保留策略
private String retentionPolicy;
private InfluxDB influxDB;
@ -37,6 +43,7 @@ public class InfluxDBService {
@PostConstruct
public void initInfluxDb() {
alarmKey2MeasurementMapping();//influxDB报警键值与表名映射
LOGGER.info("-->>开始连接influxDB数据库");
this.retentionPolicy = retentionPolicy == null || "".equals(retentionPolicy) ? "autogen" : retentionPolicy;
this.influxDB = influxDbBuild();
@ -52,6 +59,27 @@ public class InfluxDBService {
// insert(measurement, tags, fields);
// }
public void alarmKey2MeasurementMapping(){
Map<String,String> alarmKey2MeasurementMapping = new HashMap<>();
alarmKey2MeasurementMapping.put("x_push_temp", "gateway_channel_current_data");
alarmKey2MeasurementMapping.put("y_push_temp", "gateway_channel_current_data");
alarmKey2MeasurementMapping.put("x_lube_press", "gateway_channel_hydra_data");
alarmKey2MeasurementMapping.put("y_lube_press", "gateway_channel_hydra_data");
alarmKey2MeasurementMapping.put("z_lube_press", "gateway_channel_hydra_data");
alarmKey2MeasurementMapping.put("at_temp", "gateway_channel_temp_data");
alarmKey2MeasurementMapping.put("x_bear_temp", "gateway_channel_temp_data");
alarmKey2MeasurementMapping.put("y_bear_temp", "gateway_channel_temp_data");
alarmKey2MeasurementMapping.put("z_bear_temp", "gateway_channel_temp_data");
alarmKey2MeasurementMapping.put("x_debris_temp", "gateway_channel_temp_data");
alarmKey2MeasurementMapping.put("z_debris_temp", "gateway_channel_temp_data");
alarmKey2MeasurementMapping.put("x_entropy", "gateway_channel_vibr_data");
alarmKey2MeasurementMapping.put("on_time", "gateway_channel_work_data");
alarmKey2MeasurementMapping.put("work_items", "gateway_channel_work_data");
alarmKey2MeasurementMapping.put("work_time", "gateway_channel_work_data");
alarmKey2MeasurementMapping.put("work_total", "gateway_channel_work_data");
stringRedisTemplate.opsForValue().set("alarmKey2MeasurementMapping", JsonUtils.toJsonString(alarmKey2MeasurementMapping));
}
/**
* 设置数据保存策略 defalut 策略名 /database 数据库名/ 30d 数据保存时限30天/ 1 副本个数为1/ 结尾DEFAULT
* 表示 设为默认的策略

View File

@ -135,6 +135,7 @@ public class AlarmRulesApiImpl implements AlarmRulesApi{
updateData.setLastAlarmTime(LocalDateTime.now());
updateData.setAlarmLevel(rule.getAlarmLevel());
updateData.setEquipId(rule.getEquipId());
updateData.setReasonDescription(rule.getReasonDescription());
alarmDataService.updateAlarmData(updateData);
}

View File

@ -1,6 +1,6 @@
import request from "@/utils/request";
// 根据时间查询超压底缸数据
// 根据时间查询数据
export function getDataList(params) {
return request({
url: "/data/query/list",
@ -9,6 +9,15 @@ export function getDataList(params) {
});
}
// 根据时间查询参数的数据
export function getFieldList(params) {
return request({
url: "/data/query/getFieldList",
method: "get",
params: params,
});
}
// 导出机床电流参数报警记录 Excel
export function exportCurrentDataExcel(params) {
return request({

View File

@ -103,7 +103,14 @@
label="等级"
align="center"
prop="alarmLevel"
/>
>
<template v-slot="scope">
<dict-tag
:type="DICT_TYPE.ALARM_LEVEL"
:value="scope.row.alarmLevel"
/>
</template>
</el-table-column>
<el-table-column
label="客户名"
align="center"
@ -184,13 +191,13 @@
size="mini"
type="text"
icon="el-icon-data-line"
@click="handleAlarmTrend(scope.row.nameKey)"
@click="handleAlarmTrend(scope.row)"
>报警趋势</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-tickets"
@click="handleReason"
@click="handleReason(scope.row)"
>报警原因参考</el-button>
</template>
</el-table-column>
@ -328,14 +335,14 @@
size="mini"
type="text"
icon="el-icon-data-line"
@click="handleAlarmTrend"
@click="handleAlarmTrend(scope.row)"
>报警趋势</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-tickets"
@click="handleReason"
>报警原因参考</el-button>
@click="handleReason(scope.row)"
>故障原因参考</el-button>
</template>
</el-table-column>
</el-table>
@ -369,12 +376,142 @@
</div>
</div>
</div>
<!--规则详情弹窗框-->
<el-dialog
:title="detailTitle"
:visible.sync="detailOpen"
width="40%"
:close-on-click-modal="false"
>
<el-form
:model="detailForm"
v-loading="detailLoading"
label-width="100px"
>
<el-col :span="12">
<el-form-item
label="客户名称"
prop="customerName"
>
{{ detailForm.customerName }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="机床型号"
prop="equipNo"
>
{{ detailForm.modelName }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="设备编号"
prop="equipNo"
>
{{ detailForm.equipNo }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="规则参数"
prop="nameKey"
>
{{ detailForm.nameKey }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="报警内容"
prop="content"
>
{{ detailForm.content }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="报警值"
prop="alarmValue"
>
{{ detailForm.alarmValue }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="首次报警时间"
prop="firstAlarmTime"
>
{{ parseTime(detailForm.firstAlarmTime) }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="最新报警时间"
prop="lastAlarmTime"
>
{{ parseTime(detailForm.lastAlarmTime) }}
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item
label="可能故障原因"
prop="reasonDescription"
>
<div v-html="detailForm.reasonDescription" />
</el-form-item>
</el-col>
</el-form>
</el-dialog>
<!--报警参数趋势查询弹窗-->
<el-dialog
:title="alarmDataTitle"
:visible.sync="alarmDataOpen"
:close-on-click-modal="false"
width="1000px"
:before-close="beforeAlarmDataClose"
center
>
<div
class="block"
style="text-align:center;"
>
<el-date-picker
v-model="datetimeRange"
type="datetimerange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
align="right"
style="margin-right:10px"
>
</el-date-picker>
<el-button
type="primary"
icon="el-icon-search"
size="mini"
@click="handleAlarmDataQuery(null)"
> </el-button>
<el-button
icon="el-icon-refresh"
size="mini"
@click="resetAlarmDataQuery"
> </el-button>
</div>
<div
v-loading="loading"
ref="chart"
style="height:500px"
></div>
</el-dialog>
</div>
</template>
<script>
import * as echarts from "echarts";
import * as AlarmDataApi from "@/api/system/alarm/alarmdata";
import AlarmDataForm from "./AlarmDataForm.vue";
import { getFieldList } from "@/api/data/query.js";
export default {
name: "AlarmData",
components: {
@ -414,6 +551,7 @@ export default {
lastAlarmTime: [],
createTime: [],
equipAlarmId: null,
alarmType: 0,
},
treQueryParams: {
pageNo: 1,
@ -427,6 +565,7 @@ export default {
lastAlarmTime: [],
createTime: [],
equipAlarmId: null,
alarmType: 1,
},
thrActiveName: "threshold",
treActiveName: "trend",
@ -434,6 +573,23 @@ export default {
modelName: null,
equipNo: null,
alarmLevel: null,
//
dataQueryParams: {
equipId: null,
nameKey: null,
startTime: null,
endTime: null,
},
detailTitle: null,
detailOpen: false,
detailForm: {},
detailLoading: false,
//
alarmDataTitle: null,
alarmDataOpen: false,
loading: false,
selectedRow: {},
datetimeRange: [],
};
},
created() {
@ -445,10 +601,66 @@ export default {
this.alarmLevel = this.$route.query.alarmLevel;
},
methods: {
async handleAlarmDataQuery(row) {
//row
this.loading = true;
//
this.dataQueryParams.nameKey = row
? row.nameKey
: this.selectedRow.nameKey;
if (this.datetimeRange == null || this.datetimeRange.length == 0) {
this.datetimeRange = this.getDefaultTimeRange(row.lastAlarmTime);
} else if (row == null) {
this.datetimeRange = this.formatTimeRange(this.datetimeRange);
}
this.dataQueryParams.startTime = this.datetimeRange[0];
this.dataQueryParams.endTime = this.datetimeRange[1];
const data = await getFieldList(this.dataQueryParams);
console.log("9999:", data.data);
this.getEchartData(data.data);
},
getEchartData(data) {
if (data == null) {
this.initchart([], [], "", "");
} else {
this.initchart(data.x, data.y, "", "");
}
},
resetAlarmDataQuery() {},
/**报警原因 */
handleReason() {},
handleReason(row) {
this.detailLoading = true;
this.detailTitle = "报警可能原因";
this.detailOpen = true;
this.detailForm = row;
this.detailLoading = false;
},
/**报警趋势 */
handleAlarmTrend(key) {},
handleAlarmTrend(row) {
this.selectedRow = row;
this.alarmDataOpen = true;
this.alarmDataTitle = "参数报警趋势";
this.datetimeRange = this.getDefaultTimeRange(row.lastAlarmTime); //
this.handleAlarmDataQuery(row);
},
getDefaultTimeRange(time) {
const alarmTime = new Date(time);
const hoursAgo = new Date(alarmTime.getTime() - 1 * 60 * 60 * 1000);
return [hoursAgo.toISOString(), alarmTime.toISOString()];
},
formatTimeRange(datetimerange) {
if (datetimerange || datetimerange.length != 0) {
// const startTime = new Date(datetimerange[0]);
return [datetimerange[0].toISOString(), datetimerange[1].toISOString()];
}
return null;
},
beforeAlarmDataClose() {
// this.chart && this.chart.dispose();
// done();
this.alarmDataOpen = false;
},
/**
* 全部关闭报警
*/
@ -457,6 +669,157 @@ export default {
* 维修工单
*/
handelMaintanence() {},
/**echart表初始化 */
initchart(xData, yData, name, unit) {
let p = new Promise((resolve) => {
resolve();
});
p.then(() => {
this.chart = echarts.init(this.$refs.chart);
let option = {
color: ["#637DCB"],
title: {
text: "",
right: "100",
textStyle: { fontWeight: "normal", color: "#637DCB" },
},
toolbox: {
top: 0,
itemSize: 13, // icon
iconStyle: {
color: "transparent",
borderColor: "#fff",
},
feature: {
dataZoom: {
yAxisIndex: "none",
},
restore: { show: false },
saveAsImage: { show: false },
},
},
tooltip: {
//
trigger: "axis",
axisPointer: {
type: "line", // line线 shadow
},
backgroundColor: "#fff",
extraCssText: "box-shadow: 4px 4px 4px 2px rgba(0, 0, 0, 0.2)", //
textStyle: {
color: "#4E5969", //
},
formatter: (prams) => {
let name = parseFloat(_.get(prams, [0, "name"])).toFixed(4);
let value = parseFloat(_.get(prams, [0, "value"])).toFixed(4);
return (
"<span style='color:#6D85CE'>" +
`${name}` +
"," +
`${value}` +
"</span>"
);
},
},
xAxis: [
{
type: "category",
name: "时间",
data: xData,
nameTextStyle: { fontSize: "9" },
axisLine: {
show: true,
lineStyle: {
color: "#fff",
width: 0, //
},
},
axisLabel: {
textStyle: {
color: "#fff",
fontSize: "9",
},
showMaxLabel: true,
showMinLabel: true,
},
},
],
yAxis: [
{
name: "单位: " + unit,
type: "value",
nameLocation: "end",
nameTextStyle: {
color: "#fff",
fontSize: "9",
},
axisLine: {
show: false,
},
axisTick: {
show: false,
},
axisLabel: {
interval: 0,
textStyle: {
fontSize: "9",
color: "#fff",
},
},
splitLine: {
//线
show: true, //
lineStyle: {
color: "#3C506B", //线
type: "dashed", //线dashed线
},
},
},
],
grid: {
left: "3%",
right: "8%",
bottom: "18%",
top: "14%",
containLabel: true,
},
dataZoom: [
{
show: true,
type: "slider",
xAxisIndex: 0,
filterMode: "none",
start: 0,
end: xData.length,
bottom: "1%",
fillerColor: "rgba(167,183,204,0.4)", //
borderColor: "#3C506B",
},
{
show: true,
type: "inside",
xAxisIndex: 0,
filterMode: "none",
start: 0,
end: 10,
bottom: "1%",
fillerColor: "rgba(167,183,204,0.4)", //
borderColor: "#E0E6F3",
},
],
series: [
{
name: name,
type: "line",
data: yData,
symbol: "none",
},
],
};
this.chart.setOption(option);
this.loading = false;
});
},
/** 查询列表 */
async getThrList() {
try {
@ -523,7 +886,7 @@ export default {
.company-line-wrap-info {
height: 100%;
display: grid;
grid-template-rows: 9% 42% 42% 3%;
grid-template-rows: 9% 45% 45% 3%;
grid-gap: 1%;
//min-height: calc(100vh - 90px);
margin: 0 40px;