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 porder;
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 {
private Long iotModelId;
private String iodModelCode;
private ConcurrentHashMap<String,IotFieldInfoCache> fieldInfoCache;
}

View File

@ -1,5 +1,7 @@
package com.das.modules.cache.service;
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;
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.equipment.mapper.SysIotModelMapper;
import jakarta.annotation.PostConstruct;
@ -7,18 +9,70 @@ import jakarta.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
@Service
public class IotModelCacheImpl implements IotModelCache {
@Autowired
SysIotModelMapper sysIotModelMapper;
private ConcurrentHashMap<String, IotFieldInfoCache> iotFieldsMap = new ConcurrentHashMap<>();
private ConcurrentHashMap<Long, IotModelInfoCache> iotModelInfoIdMap = new ConcurrentHashMap<>();
@PostConstruct
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
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.data.service.DataService;
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.FunctionUtils;
import com.googlecode.aviator.runtime.type.AviatorObject;
import com.googlecode.aviator.runtime.type.AviatorRuntimeJavaType;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
@ -56,6 +58,7 @@ public class FunctionSaveCalcData extends AbstractVariadicFunction {
// return AviatorRuntimeJavaType.valueOf(result);
// }
@SneakyThrows
@Override
public AviatorObject variadicCall(Map<String, Object> env, AviatorObject... args) {
if (args.length < 4) { return AviatorRuntimeJavaType.valueOf(1);}
@ -63,6 +66,9 @@ public class FunctionSaveCalcData extends AbstractVariadicFunction {
//deviceCode
String code = (String)args[0].getValue(env);
DeviceInfoCache deviceInfoCache = cacheService.getEquipmentCache().getDeviceInfoCacheByCode(code);
if(deviceInfoCache == null) {
throw new StandardError(String.format("设备%s不存在", code));
}
List<CalculateRTData> dataList = new ArrayList<>();
for (int i = 1; i < args.length; i+=3) {
Date date = (Date)FunctionUtils.getJavaObject(args[i], env);

View File

@ -77,8 +77,20 @@ public class FunctionTopValue extends AbstractFunction {
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获取数据
return AviatorRuntimeJavaType.valueOf(1);
return AviatorRuntimeJavaType.valueOf(value);
}
@Data

View File

@ -261,11 +261,18 @@ public class DataServiceImpl implements DataService {
*/
@Override
public Double getTimeTopValue(Long devcieId, String attr, long startTime, long endTime){
DeviceInfoCache deviceInfoCacheById = cacheService.getEquipmentCache().getDeviceInfoCacheById(devcieId);
if (deviceInfoCacheById == null) {
DeviceInfoCache deviceInfoCache = cacheService.getEquipmentCache().getDeviceInfoCacheById(devcieId);
if (deviceInfoCache == null) {
return null;
}
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);
}
}

View File

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

View File

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