This commit is contained in:
高云鹏 2024-11-27 13:59:50 +08:00
commit 8cae89c876
9 changed files with 244 additions and 133 deletions

View File

@ -9,5 +9,17 @@ public class IotFieldInfoCache {
private Integer attributeType; private Integer attributeType;
private Integer porder; private Integer porder;
private Integer highspeed; private Integer highspeed;
private Integer datatype; private String datatype;
public boolean isHighSpeed() {
return highspeed.equals(1) && attributeType.equals(138);
}
public boolean isLowSpeed() {
return highspeed.equals(0) && attributeType.equals(138);
}
public boolean isCalculate() {
return attributeType.equals(139);
}
} }

View File

@ -8,5 +8,4 @@ import java.util.concurrent.ConcurrentHashMap;
public class IotModelInfoCache { public class IotModelInfoCache {
private Long iotModelId; private Long iotModelId;
private String iodModelCode; private String iodModelCode;
private ConcurrentHashMap<String,IotFieldInfoCache> fieldInfoCache;
} }

View File

@ -1,5 +1,7 @@
package com.das.modules.cache.service; package com.das.modules.cache.service;
public interface IotModelCache { public interface IotModelCache {
public boolean isHighSpeed(Long modelId, String attr);
public boolean isLowSpeed(Long modelId, String attr);
public boolean isCalculate(Long modelId, String attr);
} }

View File

@ -1,5 +1,7 @@
package com.das.modules.cache.service.impl; package com.das.modules.cache.service.impl;
import com.das.modules.cache.domain.IotFieldInfoCache;
import com.das.modules.cache.domain.IotModelInfoCache;
import com.das.modules.cache.service.IotModelCache; import com.das.modules.cache.service.IotModelCache;
import com.das.modules.equipment.mapper.SysIotModelMapper; import com.das.modules.equipment.mapper.SysIotModelMapper;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
@ -7,18 +9,70 @@ import jakarta.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
@Service @Service
public class IotModelCacheImpl implements IotModelCache { public class IotModelCacheImpl implements IotModelCache {
@Autowired @Autowired
SysIotModelMapper sysIotModelMapper; SysIotModelMapper sysIotModelMapper;
private ConcurrentHashMap<String, IotFieldInfoCache> iotFieldsMap = new ConcurrentHashMap<>();
private ConcurrentHashMap<Long, IotModelInfoCache> iotModelInfoIdMap = new ConcurrentHashMap<>();
@PostConstruct @PostConstruct
public void init(){ public void init(){
//缓存模型
sysIotModelMapper.getAllIotModel().forEach(item -> {
IotModelInfoCache info = new IotModelInfoCache();
info.setIotModelId(item.getId());
info.setIodModelCode(item.getIotModelCode());
iotModelInfoIdMap.put(item.getId(), info);
});
//缓存模型属性
iotModelInfoIdMap.forEach((k,v)->{
sysIotModelMapper.queryFieldByModelId(k).forEach(item -> {
IotFieldInfoCache fieldInfoCache = new IotFieldInfoCache();
fieldInfoCache.setAttributeCode(item.getAttributeCode());
fieldInfoCache.setPorder(item.getPorder());
fieldInfoCache.setAttributeName(item.getAttributeName());
fieldInfoCache.setAttributeType(item.getAttributeType());
fieldInfoCache.setHighspeed(item.getHighSpeed());
fieldInfoCache.setDatatype(item.getDataType());
iotFieldsMap.put(String.format("%d_%s", k, item.getAttributeCode()), fieldInfoCache);
});
});
} }
@PreDestroy @PreDestroy
public void destroy(){ public void destroy(){
} }
@Override
public boolean isHighSpeed(Long modelId, String attr) {
IotFieldInfoCache fieldInfoCache = iotFieldsMap.get(String.format("%d_%s", modelId, attr));
if (fieldInfoCache == null) {
return false;
}
return fieldInfoCache.isHighSpeed();
}
@Override
public boolean isLowSpeed(Long modelId, String attr) {
IotFieldInfoCache fieldInfoCache = iotFieldsMap.get(String.format("%d_%s", modelId, attr));
if (fieldInfoCache == null) {
return false;
}
return fieldInfoCache.isLowSpeed();
}
@Override
public boolean isCalculate(Long modelId, String attr) {
IotFieldInfoCache fieldInfoCache = iotFieldsMap.get(String.format("%d_%s", modelId, attr));
if (fieldInfoCache == null) {
return false;
}
return fieldInfoCache.isCalculate();
}
} }

View File

@ -4,10 +4,12 @@ import com.das.modules.cache.domain.DeviceInfoCache;
import com.das.modules.cache.service.CacheService; import com.das.modules.cache.service.CacheService;
import com.das.modules.data.service.DataService; import com.das.modules.data.service.DataService;
import com.das.modules.node.domain.bo.CalculateRTData; import com.das.modules.node.domain.bo.CalculateRTData;
import com.googlecode.aviator.exception.StandardError;
import com.googlecode.aviator.runtime.function.AbstractVariadicFunction; import com.googlecode.aviator.runtime.function.AbstractVariadicFunction;
import com.googlecode.aviator.runtime.function.FunctionUtils; import com.googlecode.aviator.runtime.function.FunctionUtils;
import com.googlecode.aviator.runtime.type.AviatorObject; import com.googlecode.aviator.runtime.type.AviatorObject;
import com.googlecode.aviator.runtime.type.AviatorRuntimeJavaType; import com.googlecode.aviator.runtime.type.AviatorRuntimeJavaType;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.util.*; import java.util.*;
@ -56,6 +58,7 @@ public class FunctionSaveCalcData extends AbstractVariadicFunction {
// return AviatorRuntimeJavaType.valueOf(result); // return AviatorRuntimeJavaType.valueOf(result);
// } // }
@SneakyThrows
@Override @Override
public AviatorObject variadicCall(Map<String, Object> env, AviatorObject... args) { public AviatorObject variadicCall(Map<String, Object> env, AviatorObject... args) {
if (args.length < 4) { return AviatorRuntimeJavaType.valueOf(1);} if (args.length < 4) { return AviatorRuntimeJavaType.valueOf(1);}
@ -63,6 +66,9 @@ public class FunctionSaveCalcData extends AbstractVariadicFunction {
//deviceCode //deviceCode
String code = (String)args[0].getValue(env); String code = (String)args[0].getValue(env);
DeviceInfoCache deviceInfoCache = cacheService.getEquipmentCache().getDeviceInfoCacheByCode(code); DeviceInfoCache deviceInfoCache = cacheService.getEquipmentCache().getDeviceInfoCacheByCode(code);
if(deviceInfoCache == null) {
throw new StandardError(String.format("设备%s不存在", code));
}
List<CalculateRTData> dataList = new ArrayList<>(); List<CalculateRTData> dataList = new ArrayList<>();
for (int i = 1; i < args.length; i+=3) { for (int i = 1; i < args.length; i+=3) {
Date date = (Date)FunctionUtils.getJavaObject(args[i], env); Date date = (Date)FunctionUtils.getJavaObject(args[i], env);

View File

@ -77,8 +77,20 @@ public class FunctionTopValue extends AbstractFunction {
return AviatorRuntimeJavaType.valueOf(cacheValue.value); return AviatorRuntimeJavaType.valueOf(cacheValue.value);
} }
} }
else {
cacheValue = new CacheValue();
}
Date startTime = DateUtil.beginOfDay(curTimeValue);
Date endTime = DateUtil.endOfDay(curTimeValue);
Double value = dataService.getTimeTopValue(deviceInfoCache.getDeviceId(), attrName, startTime.getTime(), endTime.getTime());
if (value == null){
return AviatorNil.NIL;
}
cacheValue.setValue(value);
cacheValue.setCurTimeValue(curTimeValue);
cacheValues.put(key, cacheValue);
//未找到缓存查询时序API获取数据 //未找到缓存查询时序API获取数据
return AviatorRuntimeJavaType.valueOf(1); return AviatorRuntimeJavaType.valueOf(value);
} }
@Data @Data

View File

@ -261,11 +261,18 @@ public class DataServiceImpl implements DataService {
*/ */
@Override @Override
public Double getTimeTopValue(Long devcieId, String attr, long startTime, long endTime){ public Double getTimeTopValue(Long devcieId, String attr, long startTime, long endTime){
DeviceInfoCache deviceInfoCacheById = cacheService.getEquipmentCache().getDeviceInfoCacheById(devcieId); DeviceInfoCache deviceInfoCache = cacheService.getEquipmentCache().getDeviceInfoCacheById(devcieId);
if (deviceInfoCacheById == null) { if (deviceInfoCache == null) {
return null; return null;
} }
String tableName = ""; String tableName = "";
if (cacheService.getIotModelCache().isHighSpeed(deviceInfoCache.getIotModelId(), attr)){
tableName = String.format("h_%s", deviceInfoCache.getDeviceId());
} else if (cacheService.getIotModelCache().isLowSpeed(deviceInfoCache.getIotModelId(), attr)){
tableName = String.format("l_%s", deviceInfoCache.getDeviceId());
} else if (cacheService.getIotModelCache().isCalculate(deviceInfoCache.getIotModelId(), attr)){
tableName = String.format("c_%s", deviceInfoCache.getDeviceId());
}
return tdEngineService.getTimeTopValue(tableName, attr, startTime, endTime); return tdEngineService.getTimeTopValue(tableName, attr, startTime, endTime);
} }
} }

View File

@ -2,16 +2,12 @@
<div class="statAnalysis"> <div class="statAnalysis">
<el-menu :default-active="activeIndex" class="headerList" mode="horizontal" @select="handleSelect"> <el-menu :default-active="activeIndex" class="headerList" mode="horizontal" @select="handleSelect">
<el-menu-item index="0" key="0"> {{ headerList[0] }} </el-menu-item> <el-menu-item index="0" key="0"> {{ headerList[0] }} </el-menu-item>
<el-popover placement="top-start" :width="200" popper-class="admin-info-box" trigger="hover" content="同一测点,不同时间段对比"> <el-tooltip class="box-item" effect="dark" content="同一测点,不同时间段对比" placement="right-start">
<template #reference> <el-menu-item index="1" key="1"> {{ headerList[1] }} </el-menu-item>
<el-menu-item index="1" key="1"> {{ headerList[1] }} </el-menu-item> </el-tooltip>
</template> <el-tooltip class="box-item" effect="dark" content="同一时间段,不同测点对比" placement="right-start">
</el-popover> <el-menu-item index="2" key="2"> {{ headerList[2] }} </el-menu-item>
<el-popover placement="top-start" :width="200" popper-class="admin-info-box" trigger="hover" content="同一时间段,不同测点对比"> </el-tooltip>
<template #reference>
<el-menu-item index="2" key="2"> {{ headerList[2] }} </el-menu-item>
</template>
</el-popover>
</el-menu> </el-menu>
<keep-alive> <keep-alive>

View File

@ -1,92 +1,96 @@
<template> <template>
<div class="contain"> <div class="contain">
<div class="left"> <div class="headerPart">
<div class="headerPart"> <div class="topLeft">
<div class="topLeft"> <div class="selectPart">
<div class="selectPart"> <span>{{ t('statAnalysis.deviceId') }}</span>
<span>{{ t('statAnalysis.deviceId') }}</span> <el-select
<el-select v-model="statAnalysisSelect.deviceId"
v-model="statAnalysisSelect.deviceId" @change="deviceIdChange"
@change="deviceIdChange" :placeholder="'请选择' + t('statAnalysis.deviceId')"
:placeholder="'请选择' + t('statAnalysis.deviceId')" class="statAnalysisSelect"
class="statAnalysisSelect" >
> <el-option v-for="v in statAnalysisSelectOptions.deviceId" :key="v.value" :label="v.label" :value="v.value"></el-option>
<el-option v-for="v in statAnalysisSelectOptions.deviceId" :key="v.value" :label="v.label" :value="v.value"></el-option> </el-select>
</el-select>
</div>
<div class="selectPart">
<span>{{ t('statAnalysis.attributes') }}</span>
<el-input
class="statAnalysisSelect"
v-model="statAnalysisSelect.attributes"
@click="attributesChange"
:placeholder="'请选择' + t('statAnalysis.attributes')"
></el-input>
</div>
<div class="selectPart">
<span>{{ t('statAnalysis.interval') }}</span>
<el-select
v-model="statAnalysisSelect.interval"
:placeholder="'请选择' + t('statAnalysis.interval')"
class="statAnalysisSelect"
>
<el-option v-for="v in statAnalysisSelectOptions.interval" :key="v.value" :label="v.label" :value="v.value"></el-option>
</el-select>
</div>
</div> </div>
<div class="topRight"> <div class="selectPart">
<el-button type="primary" :icon="Plus" @click="addTime()"> 增加</el-button> <span>{{ t('statAnalysis.attributes') }}</span>
<el-button type="primary" :loading="isLoading" @click="statAnalysisOperate()">{{ t('statAnalysis.search') }}</el-button> <el-input
<el-button style="color: #0064aa" @click="statAnalysisExport()">{{ t('statAnalysis.export') }}</el-button> class="statAnalysisSelect"
v-model="statAnalysisSelect.attributes"
@click="attributesChange"
:placeholder="'请选择' + t('statAnalysis.attributes')"
></el-input>
</div> </div>
<div class="selectPart">
<span>{{ t('statAnalysis.interval') }}</span>
<el-select v-model="statAnalysisSelect.interval" :placeholder="'请选择' + t('statAnalysis.interval')" class="statAnalysisSelect">
<el-option v-for="v in statAnalysisSelectOptions.interval" :key="v.value" :label="v.label" :value="v.value"></el-option>
</el-select>
</div>
<el-button class="addline" type="primary" :icon="Plus" @click="addTime()"> 增加</el-button>
</div> </div>
<div class="topRight">
<div class="timeColumns"> <el-button type="primary" :loading="isLoading" @click="statAnalysisOperate()">{{ t('statAnalysis.search') }}</el-button>
<div class="moduleRow" v-for="(time, index) in times" :key="index"> <el-button style="color: #0064aa" @click="statAnalysisExport()">{{ t('statAnalysis.export') }}</el-button>
<div class="item">
<el-icon v-show="index !== 0" class="removeModule" @click="switchTime(index)">
<Close />
</el-icon>
<div class="selectPart">
<span>曲线名称</span>
<el-input v-model="customName[index]" class="customName"></el-input>
</div>
<div class="selectPart">
<span>{{ t('statAnalysis.time') }}</span>
<el-date-picker
class="datetime-picker"
v-model="times[index]"
:type="statAnalysisSelect.interval == '1d' ? 'daterange' : 'datetimerange'"
:value-format="statAnalysisSelect.interval == '1d' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
:teleported="false"
:shortcuts="shortcuts"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]"
@change="timechange(index)"
/>
</div>
<div class="topLeft">
<div class="selectPart" v-if="calculate[index] && calculate[index]['max'] !== ''">
<span>{{ t('statAnalysis.max') }}</span>
<span class="max">{{ calculate[index]['max'] }}</span>
</div>
<div class="selectPart" v-if="calculate[index] && calculate[index]['min'] !== ''">
<span>{{ t('statAnalysis.min') }}</span>
<span class="min">{{ calculate[index]['min'] }}</span>
</div>
<div class="selectPart" v-if="calculate[index] && calculate[index]['average'] !== ''">
<span>{{ t('statAnalysis.average') }}</span>
<span class="average">{{ calculate[index]['average'] }}</span>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
<div class="right"> <div class="main">
<div <div class="left">
ref="chartContainer" <div class="timeColumns">
style="position: absolute; top: 127px; width: calc(100% - 430px); height: 400px; border: 1px solid rgb(217, 217, 217)" <div class="moduleRow" v-for="(time, index) in times" :key="index">
></div> <div class="item">
<el-icon v-show="index !== 0" class="removeModule" @click="switchTime(index)">
<Close />
</el-icon>
<div class="selectPart">
<span>曲线名称</span>
<el-input v-model="customName[index]" class="customName"></el-input>
</div>
<div class="selectPart">
<span>{{ t('statAnalysis.time') }}</span>
<el-date-picker
class="datetime-picker"
v-model="times[index]"
:type="statAnalysisSelect.interval == '1d' ? 'daterange' : 'datetimerange'"
:value-format="statAnalysisSelect.interval == '1d' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
:teleported="false"
:shortcuts="shortcuts"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]"
@change="timechange(index)"
/>
</div>
<!-- <div class="topLeft">
<div class="selectPart" v-if="calculate[index] && calculate[index]['max'] !== ''">
<span>{{ t('statAnalysis.max') }}</span>
<span class="max">{{ calculate[index]['max'] }}</span>
</div>
<div class="selectPart" v-if="calculate[index] && calculate[index]['min'] !== ''">
<span>{{ t('statAnalysis.min') }}</span>
<span class="min">{{ calculate[index]['min'] }}</span>
</div>
<div class="selectPart" v-if="calculate[index] && calculate[index]['average'] !== ''">
<span>{{ t('statAnalysis.average') }}</span>
<span class="average">{{ calculate[index]['average'] }}</span>
</div>
</div> -->
</div>
</div>
</div>
</div>
<div class="right">
<div
ref="chartContainer"
style="
position: absolute;
top: 127px;
width: calc(100% - 430px);
height: calc(100% - 187px);
border: 1px solid rgb(217, 217, 217);
margin: 30px 0;
"
></div>
</div>
</div> </div>
</div> </div>
<el-dialog v-model="showMeasure" title="测点名称" :width="800"> <el-dialog v-model="showMeasure" title="测点名称" :width="800">
@ -124,8 +128,12 @@ const statAnalysisSelect = reactive({
}) })
const times: any = reactive([{ time: '' }]) const times: any = reactive([{ time: '' }])
const addTime = () => { const addTime = () => {
times.push({ time: '' }) if (times.length < 4) {
customName.push(statAnalysisSelect.attributes + String(times.length)) times.push({ time: '' })
customName.push(statAnalysisSelect.attributes + String(times.length))
} else {
ElMessage.info('最多可添加四条查询曲线!')
}
} }
const switchTime = (index: number) => { const switchTime = (index: number) => {
times.splice(index, 1) times.splice(index, 1)
@ -421,7 +429,7 @@ const historyDataReq = (promises: any) => {
const fillData = fillMissingData(alltimes, resData) const fillData = fillMissingData(alltimes, resData)
const xData = fillData['times'] const xData = fillData['times']
const yData = fillData['values'] const yData = fillData['values']
calculate.value[index] = calculateStats(resData['values']) // calculate.value[index] = calculateStats(resData['values'])
xDatas.push({ xDatas.push({
series: String(customName[index]), series: String(customName[index]),
data: xData, data: xData,
@ -564,48 +572,63 @@ const timestampToTime = (timestamp: any) => {
height: 100%; height: 100%;
.contain { .contain {
height: calc(100% - 60px); height: calc(100% - 60px);
display: flex; // display: flex;
.right { .main {
width: calc(100% - 418px);
}
}
.headerPart {
padding: 20px;
.topLeft {
.icon {
width: 40px;
height: 40px;
padding: 10px 0;
}
.dialog-footer button:first-child {
margin-right: 10px;
}
}
.topRight {
display: flex; display: flex;
margin: 12px 0; height: calc(100% - 60px);
.el-button {
width: 100px; .right {
height: 40px; width: calc(100% - 418px);
height: 100%;
}
}
.headerPart {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
height: 60px;
.addline {
margin: 4px 20px;
}
.topLeft {
display: flex;
.icon {
width: 40px;
height: 40px;
padding: 10px 0;
}
.dialog-footer button:first-child {
margin-right: 10px;
}
}
.topRight {
display: flex;
margin: 12px 0;
.el-button {
width: 100px;
height: 40px;
}
} }
} }
} }
.timeColumns { .timeColumns {
.moduleRow { .moduleRow {
.item { .item {
position: relative; position: relative;
margin: 10px 8px; margin: 0px 12px 12px;
width: 402px; padding: 12px 12px 8px;
min-height: 170px;
padding: 20px 5px 30px 5px;
border-radius: 20px; border-radius: 20px;
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12); box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
.topLeft { .topLeft {
display: flex; display: flex;
} }
.selectPart { .selectPart {
width: 370px; width: 388px;
justify-content: left; justify-content: left;
margin: 4px;
span { span {
white-space: nowrap; white-space: nowrap;
} }
@ -614,15 +637,17 @@ const timestampToTime = (timestamp: any) => {
} }
.removeModule { .removeModule {
position: absolute; position: absolute;
top: 2px; top: 6px;
right: 5px; right: 8px;
color: rgba(0, 0, 0, 0); color: rgba(0, 0, 0, 0);
cursor: pointer; cursor: pointer;
transition: color 0.5s; transition: color 0.5s;
} }
&:hover { &:hover {
.removeModule { .removeModule {
color: rgba(0, 0, 0, 1); color: rgba(0, 0, 0, 0.55);
border: 1px solid rgba(0, 0, 0, 0.55);
border-radius: 50px;
} }
} }
.headerPart { .headerPart {
@ -635,8 +660,6 @@ const timestampToTime = (timestamp: any) => {
align-items: center; align-items: center;
height: 40px; height: 40px;
margin-right: 20px; margin-right: 20px;
width: 340px;
margin: 4px 0;
span { span {
margin-right: 10px; margin-right: 10px;
} }