完善大屏功能

This commit is contained in:
iteaj
2025-06-14 14:48:27 +08:00
parent 28ab99bfe0
commit abe920e287
17 changed files with 451 additions and 78 deletions
@@ -16,13 +16,14 @@ public class DataItemOption implements Cloneable{
private Object value;
private boolean load;
private boolean isLeaf;
private boolean disabled;
private boolean unique;
/**
* 选中为名称
*/
private boolean name;
private List<DataItemOption> children;
public static final String SPLIT = ":";
@@ -40,24 +41,22 @@ public class DataItemOption implements Cloneable{
this.isLeaf = isLeaf;
}
public DataItemOption(String label, Object value, boolean isLeaf, boolean unique) {
this.label = label;
this.value = value;
this.isLeaf = isLeaf;
this.unique = unique;
}
public DataItemOption addChild(DataItemOption option) {
if(children == null) {
children = new ArrayList<>();
}
children.add(option);
option.setValue(this.getValue() + SPLIT + option.getValue());
return this;
}
public DataItemOption load() {
this.load = true; return this;
public Builder builder() {
return new Builder(this);
}
public DataItemOption asName() {
this.name = true; return this;
}
public boolean getIsLeaf() {
@@ -68,4 +67,61 @@ public class DataItemOption implements Cloneable{
public DataItemOption clone() throws CloneNotSupportedException {
return (DataItemOption) super.clone();
}
public class Builder {
public Builder parent;
public DataItemOption current;
protected Builder(DataItemOption current) {
this.current = current;
}
public Builder(DataItemOption current, Builder parent) {
this.parent = parent;
this.current = current;
}
public Builder addChild(String label, Object value) {
final DataItemOption option = new DataItemOption(label, value);
current.addChild(option);
return new Builder(option, this);
}
public Builder addNameChild(String label, Object value) {
final DataItemOption option = new DataItemOption(label, value).asName();
current.addChild(option);
return new Builder(option, this);
}
public Builder addChild(String label, Object value, boolean isLeaf) {
final DataItemOption option = new DataItemOption(label, value, isLeaf);
current.addChild(option);
return new Builder(option, this);
}
public Builder addNameChild(String label, Object value, boolean isLeaf) {
final DataItemOption option = new DataItemOption(label, value, isLeaf).asName();
current.addChild(option);
return new Builder(option, this);
}
public DataItemOption build() {
return current;
}
public Builder toTop() {
Builder temp = parent;
do {
if(temp.parent == null) {
return temp;
}
} while ((temp = temp.parent) != null);
return null;
}
public Builder toParent() {
return parent;
}
}
}
@@ -0,0 +1,21 @@
package com.iteaj.framework.spi.iot.view;
import com.iteaj.framework.spi.iot.data.BaseEchartsCount;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class ListFetchResult extends ViewFetchResult {
public ListFetchResult(String item, List<BaseEchartsCount> values) {
super(item, values);
}
@Override
public List<BaseEchartsCount> getValue() {
return (List<BaseEchartsCount>) super.getValue();
}
}
@@ -0,0 +1,13 @@
package com.iteaj.framework.spi.iot.view;
public class NumberFetchResult extends ViewFetchResult{
public NumberFetchResult(String item, Number value) {
super(item, value);
}
@Override
public Number getValue() {
return (Number) super.getValue();
}
}
@@ -32,7 +32,7 @@ public interface ViewApiItem extends Ordered {
return ORDER;
}
Object fetch(ViewApiParams params);
List<ViewFetchResult> fetch(List<ViewApiParams.Value> values, ViewApiParams params);
/**
* 数据列表
@@ -1,8 +1,10 @@
package com.iteaj.framework.spi.iot.view;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.iteaj.framework.exception.ServiceException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -47,7 +49,27 @@ public final class ViewApiManager {
return viewApiItem.loadOptions(split);
}
public static Object fetch(ViewApiParams params) {
return null;
public static List<ViewFetchResult> fetch(ViewApiParams params) {
List<ViewFetchResult> result = new ArrayList<>();
final List<ViewApiParams.Value> values = params.getValues();
if(CollectionUtil.isNotEmpty(values)) {
final Map<String, List<ViewApiParams.Value>> listMap = values.stream()
.collect(Collectors.groupingBy(item -> item.getValueItem(0)));
listMap.forEach((key, valueList) -> {
if(!itemMap.containsKey(key)) {
throw new ServiceException("未找到处理接口");
}
});
listMap.forEach((key, valueList) -> {
final ViewApiItem viewApiItem = itemMap.get(key);
final List<ViewFetchResult> results = viewApiItem.fetch(valueList, params);
result.addAll(results);
});
} else {
throw new ServiceException("没有提供统计项");
}
return result;
}
}
@@ -1,9 +1,12 @@
package com.iteaj.framework.spi.iot.view;
import cn.hutool.core.util.StrUtil;
import com.iteaj.framework.exception.ServiceException;
import com.iteaj.framework.spi.iot.consts.TimeCountType;
import lombok.Getter;
import lombok.Setter;
import java.util.Arrays;
import java.util.List;
@Getter
@@ -44,9 +47,14 @@ public class ViewApiParams {
private String item;
/**
* 字段
*
*/
private String field;
private String name;
/**
* 值
*/
private String value;
/**
* 计算类型(count, min, max, avg, sum, ...)
@@ -54,11 +62,6 @@ public class ViewApiParams {
private String calcType;
/**
* 统计范围
* all 所有
* product 产品
* group 设备组
* device 指定设备
*/
private String range;
@@ -67,5 +70,24 @@ public class ViewApiParams {
*/
private String rangeValue;
private List<String> values;
public Value build() {
if(StrUtil.isNotBlank(this.getValue())) {
this.values = Arrays.asList(this.getValue().split(DataItemOption.SPLIT));
} else {
throw new ServiceException("项格式错误");
}
return this;
}
public String getValueItem(int index) {
if(values == null) {
build();
}
return this.values.get(index);
}
}
}
@@ -0,0 +1,25 @@
package com.iteaj.framework.spi.iot.view;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public abstract class ViewFetchResult {
/**
* 项
* @see ViewApiParams.Value#getItem()
*/
private String item;
/**
* 值
*/
private Object value;
public ViewFetchResult(String item, Object value) {
this.item = item;
this.value = value;
}
}
@@ -0,0 +1,21 @@
package com.iteaj.iboot.plugin.iotview;
import com.iteaj.framework.result.Result;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class FetchResult extends IotViewResult{
private String chart;
protected FetchResult(String chart, Result result) {
super(result);
this.chart = chart;
}
public static FetchResult builder(String chart, Result result) {
return new FetchResult(chart, result);
}
}
@@ -38,7 +38,16 @@ public class IotViewResult extends AbstractResult {
}
public static IotViewResult Fail(String msg) {
return Fail(msg, HttpResult.DEFAULT_ERROR_CODE);
return Fail(msg, -HttpResult.DEFAULT_ERROR_CODE);
}
/**
* 失败但前端不进行跳转失败页
* @param msg
* @return
*/
public static IotViewResult FailNotJump(String msg) {
return Fail(msg, -HttpResult.DEFAULT_ERROR_CODE);
}
public static IotViewResult Fail(String msg, int code) {
@@ -1,11 +1,14 @@
package com.iteaj.iboot.plugin.iotview.api;
import com.iteaj.framework.spi.iot.view.DataItemOption;
import com.iteaj.framework.spi.iot.view.ViewApiItem;
import com.iteaj.framework.spi.iot.view.ViewApiParams;
import cn.hutool.core.util.StrUtil;
import com.iteaj.framework.exception.ServiceException;
import com.iteaj.framework.spi.iot.view.*;
import com.iteaj.iboot.plugin.iotview.dto.DeviceStatusCountDto;
import com.iteaj.iboot.plugin.iotview.service.ISVDataService;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -16,13 +19,20 @@ public class DeviceStatusApiItem implements ViewApiItem {
private static final String NAME = "设备状态";
private final ISVDataService isvDataService;
private List<DataItemOption> options = Arrays.asList(new DataItemOption("设备状态", "STATUS")
.addChild(new DataItemOption("在线", "online")
.addChild(new DataItemOption("统计", "count", true, true))
.addChild(new DataItemOption("占比", "ratio", true, true)))
.addChild(new DataItemOption("离线", "offline")
.addChild(new DataItemOption("统计", "count", true, true))
.addChild(new DataItemOption("占比", "ratio", true, true))));
private List<DataItemOption> options = Arrays.asList(new DataItemOption("设备状态", KEY)
.asName().builder()
.addNameChild("在线", "online")
.addNameChild("统计", "count", true)
.toParent()
.addNameChild("占比", "ratio", true)
.toTop()
.addNameChild("离线", "offline")
.addNameChild("统计", "count", true)
.toParent()
.addNameChild("占比", "ratio", true)
.toTop()
.build()
);
public DeviceStatusApiItem(ISVDataService isvDataService) {
this.isvDataService = isvDataService;
@@ -44,8 +54,35 @@ public class DeviceStatusApiItem implements ViewApiItem {
}
@Override
public Object fetch(ViewApiParams params) {
return null;
public List<ViewFetchResult> fetch(List<ViewApiParams.Value> values, ViewApiParams params) {
List<ViewFetchResult> results = new ArrayList<>(values.size());
values.stream().forEach(item -> {
final String rangeConfig = item.getRangeValue();
if(StrUtil.isBlank(rangeConfig)) {
throw new ServiceException("未指定统计范围");
}
final String[] split = rangeConfig.split(DataItemOption.SPLIT);
String range = split[0]; String rangeValue = null;
if(split.length > 1) {
rangeValue = split[1];
}
final String status = item.getValueItem(1);
final String countType = item.getValueItem(2);
DeviceStatusCountDto num = isvDataService.countDeviceStatus(status, range, rangeValue);
if("count".equals(countType)) {
results.add(new NumberFetchResult(item.getItem(), num.getTotal()));
} else if("ratio".equals(countType)) {
results.add(new NumberFetchResult(item.getItem()
, new BigDecimal(num.getValue() / (num.getTotal() * 1.0))
.setScale(2, BigDecimal.ROUND_DOWN)));
} else {
}
});
return results;
}
@Override
@@ -55,6 +92,7 @@ public class DeviceStatusApiItem implements ViewApiItem {
@Override
public List<DataItemOption> loadOptions(String[] args) {
return null;
}
}
@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollectionUtil;
import com.iteaj.framework.spi.iot.view.DataItemOption;
import com.iteaj.framework.spi.iot.view.ViewApiItem;
import com.iteaj.framework.spi.iot.view.ViewApiParams;
import com.iteaj.framework.spi.iot.view.ViewFetchResult;
import com.iteaj.iboot.plugin.iotview.service.ISVDataService;
import org.springframework.stereotype.Component;
@@ -19,10 +20,10 @@ public class ModelAttrApiItem implements ViewApiItem {
private final ISVDataService isvDataService;
private List<DataItemOption> calcOptions = Arrays.asList(
new DataItemOption("数量统计", "count", true),
new DataItemOption("求平均值", "avg", true),
new DataItemOption("求最大值", "max", true),
new DataItemOption("求最小值", "min", true)
new DataItemOption("数量统计", "count", true).asName(),
new DataItemOption("求平均值", "avg", true).asName(),
new DataItemOption("求最大值", "max", true).asName(),
new DataItemOption("求最小值", "min", true).asName()
);
private List<DataItemOption> options = Arrays.asList(new DataItemOption(NAME, KEY));
@@ -40,24 +41,14 @@ public class ModelAttrApiItem implements ViewApiItem {
return NAME;
}
@Override
public Object fetch(ViewApiParams params) {
return null;
}
@Override
public int getOrder() {
return ViewApiItem.super.getOrder() + 200;
}
@Override
public ViewApiItem init() {
final DataItemOption parent = options.get(0);
final List<DataItemOption> dataItemOptions = isvDataService.listProductOption();
if(CollectionUtil.isNotEmpty(dataItemOptions)) {
dataItemOptions.forEach(parent::addChild);
}
return this;
public List<ViewFetchResult> fetch(List<ViewApiParams.Value> values, ViewApiParams params) {
return null;
}
@Override
@@ -67,15 +58,33 @@ public class ModelAttrApiItem implements ViewApiItem {
@Override
public List<DataItemOption> loadOptions(String[] args) {
final List<DataItemOption> dataItemOptions = isvDataService.listModelAttrOption(args[1]);
if(CollectionUtil.isNotEmpty(dataItemOptions)) {
dataItemOptions.forEach(item -> {
calcOptions.forEach(calc -> item.addChild(calc));
});
List<DataItemOption> results = Collections.emptyList();
if(args.length == 1) {
final List<DataItemOption> dataItemOptions = isvDataService.listProductOption(false);
if(CollectionUtil.isNotEmpty(dataItemOptions)) {
results = dataItemOptions;
dataItemOptions.forEach(child -> child.setValue(KEY + DataItemOption.SPLIT + child.getValue()).asName());
}
} else if(args.length == 2) {
final List<DataItemOption> dataItemOptions = isvDataService.listModelAttrOption(args[1]);
if(CollectionUtil.isNotEmpty(dataItemOptions)) {
results = dataItemOptions;
final String keyPrefix = String.join(DataItemOption.SPLIT, args);
dataItemOptions.forEach(item -> {
item.setValue(keyPrefix + DataItemOption.SPLIT + item.getValue());
calcOptions.forEach(calc -> {
try {
item.asName().addChild(calc.clone());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
});
});
}
} else {
return Collections.emptyList();
}
return dataItemOptions;
return results;
}
}
@@ -1,16 +1,20 @@
package com.iteaj.iboot.plugin.iotview.controller;
import cn.hutool.core.collection.CollectionUtil;
import com.iteaj.framework.result.Result;
import com.iteaj.framework.spi.iot.view.ViewApiManager;
import com.iteaj.framework.spi.iot.view.ViewApiParams;
import com.iteaj.framework.spi.iot.view.ViewFetchResult;
import com.iteaj.iboot.plugin.iotview.FetchResult;
import com.iteaj.iboot.plugin.iotview.IotViewResult;
import com.iteaj.framework.spi.iot.view.DataItemOption;
import com.iteaj.iboot.plugin.iotview.service.ISVDataService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* 大屏数据管理
@@ -19,10 +23,10 @@ import java.util.List;
@RequestMapping("/isv/data")
public class ISVDataController {
private final ISVDataService ISVDataService;
private final ISVDataService isvDataService;
public ISVDataController(ISVDataService ISVDataService) {
this.ISVDataService = ISVDataService;
public ISVDataController(ISVDataService isvDataService) {
this.isvDataService = isvDataService;
}
/**
@@ -42,4 +46,76 @@ public class ISVDataController {
public Result<List<DataItemOption>> load(String key) {
return IotViewResult.Success(ViewApiManager.loadOptions(key));
}
/**
* 基础范围项
* @return
*/
@GetMapping("/range/basic")
public Result<List<DataItemOption>> basic() {
List<DataItemOption> result = new ArrayList<>();
final DataItemOption product = new DataItemOption("按产品", "product").asName();
final DataItemOption group = new DataItemOption("按分组", "group").asName();
final DataItemOption device = new DataItemOption("按设备", "device").asName();
result.add(product);
result.add(group);
result.add(device);
result.add(0, new DataItemOption("所有设备", "all", true).asName());
final List<DataItemOption> groupOptions = isvDataService.listGroupOption();
final List<DataItemOption> productOptions = isvDataService.listProductOption(true);
if(CollectionUtil.isNotEmpty(groupOptions)) {
groupOptions.forEach(item -> group.addChild(item.setLeaf(true).asName()));
}
if(CollectionUtil.isNotEmpty(productOptions)) {
productOptions.forEach(item -> {
try {
product.addChild(item.clone().asName().setLeaf(true));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
});
productOptions.forEach(item -> {
try {
device.addChild(item.clone().asName());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
});
}
return IotViewResult.Success(result);
}
/**
* 加载范围配置项
* @return
*/
@GetMapping("range/load")
public Result<List<DataItemOption>> rangeLoad(String key) {
final String[] args = key.split(DataItemOption.SPLIT);
if(Objects.equals(args[0], "device")) {
List<DataItemOption> devices = isvDataService.listDeviceOptions(args[1]);
if(CollectionUtil.isNotEmpty(devices)) {
}
}
return IotViewResult.Success(Collections.EMPTY_LIST);
}
/**
* 获取数据
* @param params
* @return
*/
@PostMapping("fetch")
public Result<List<ViewFetchResult>> fetch(@RequestBody ViewApiParams params) {
try {
final List<ViewFetchResult> results = ViewApiManager.fetch(params);
return FetchResult.builder(params.getChart(), IotViewResult.Success(results));
} catch (Exception e) {
return FetchResult.FailNotJump("获取数据异常");
}
}
}
@@ -0,0 +1,19 @@
package com.iteaj.iboot.plugin.iotview.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class DeviceStatusCountDto {
/**
* 项值
*/
private int value;
/**
* 总值
*/
private int total;
}
@@ -2,13 +2,17 @@ package com.iteaj.iboot.plugin.iotview.mapper;
import com.baomidou.mybatisplus.core.mapper.Mapper;
import com.iteaj.framework.spi.iot.view.DataItemOption;
import com.iteaj.iboot.plugin.iotview.dto.DeviceStatusCountDto;
import java.util.List;
public interface ISVDataMapper extends Mapper {
List<DataItemOption> listProductOption();
List<DataItemOption> listProductOption(boolean hasGateway);
List<DataItemOption> listModelAttrOption(String productId);
List<DataItemOption> listModelAttrOption(String productCode);
List<DataItemOption> listGroupOption();
DeviceStatusCountDto countDeviceStatus(String status, String range, String rangeValue);
}
@@ -1,12 +1,19 @@
package com.iteaj.iboot.plugin.iotview.service;
import com.iteaj.framework.spi.iot.view.DataItemOption;
import com.iteaj.iboot.plugin.iotview.dto.DeviceStatusCountDto;
import java.util.List;
public interface ISVDataService {
List<DataItemOption> listProductOption();
List<DataItemOption> listProductOption(boolean isGateway);
List<DataItemOption> listModelAttrOption(String productId);
List<DataItemOption> listGroupOption();
List<DataItemOption> listDeviceOptions(String productCode);
DeviceStatusCountDto countDeviceStatus(String status, String range, String rangeValue);
}
@@ -1,6 +1,7 @@
package com.iteaj.iboot.plugin.iotview.service.impl;
import com.iteaj.framework.spi.iot.view.DataItemOption;
import com.iteaj.iboot.plugin.iotview.dto.DeviceStatusCountDto;
import com.iteaj.iboot.plugin.iotview.mapper.ISVDataMapper;
import com.iteaj.iboot.plugin.iotview.service.ISVDataService;
import org.springframework.stereotype.Service;
@@ -17,12 +18,27 @@ public class ISVDataServiceImpl implements ISVDataService {
}
@Override
public List<DataItemOption> listProductOption() {
return isvDataMapper.listProductOption();
public List<DataItemOption> listProductOption(boolean isGateway) {
return isvDataMapper.listProductOption(isGateway);
}
@Override
public List<DataItemOption> listModelAttrOption(String productId) {
return isvDataMapper.listModelAttrOption(productId);
public List<DataItemOption> listModelAttrOption(String productCode) {
return isvDataMapper.listModelAttrOption(productCode);
}
@Override
public List<DataItemOption> listGroupOption() {
return isvDataMapper.listGroupOption();
}
@Override
public List<DataItemOption> listDeviceOptions(String productCode) {
return null;
}
@Override
public DeviceStatusCountDto countDeviceStatus(String status, String range, String rangeValue) {
return isvDataMapper.countDeviceStatus(status, range, rangeValue);
}
}
@@ -5,20 +5,35 @@
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.iteaj.framework.spi.iot.view.DataItemOption">
<result property="value" column="code" />
<result property="load" column="loaded" />
<result property="label" column="name" />
<result property="label" column="label" />
<result property="leaf" column="leaf" />
<result property="disabled" column="disabled" />
</resultMap>
<select id="listProductOption" resultMap="BaseResultMap">
select a.name, a.id code, count(b.id) > 0 loaded, count(b.id) = 0 leaf from iot_product a
left join iot_model_attr b on a.id = b.product_id and b.data_type != 'json'
where device_type != 'Gateway' group by a.id
select a.name label, a.code from iot_product a
<where>
<if test="hasGateway == false">
and device_type != 'Gateway'
</if>
</where>
group by a.id order by a.device_type asc, create_time desc
</select>
<select id="listModelAttrOption" resultMap="BaseResultMap">
select name, field code from iot_model_attr
where product_id = #{productId} and data_type != 'json'
select a.name label, a.field code from iot_model_attr a
left join iot_product b on a.product_id = b.id
where b.code = #{productCode} and data_type != 'json' and b.id is not null
order by a.create_time desc
</select>
<select id="listGroupOption" resultMap="BaseResultMap">
select name label, id code from iot_device_group order by create_time desc
</select>
<select id="countDeviceStatus" resultType="com.iteaj.iboot.plugin.iotview.dto.DeviceStatusCountDto">
select count(if(status=#{status}, 1, null)) value, count(1) total from iot_device
<where>
</where>
</select>
</mapper>