diff --git a/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsPointConfigController.java b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsPointConfigController.java new file mode 100644 index 0000000..f9d530e --- /dev/null +++ b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsPointConfigController.java @@ -0,0 +1,96 @@ +package com.xzzn.web.controller.ems; + +import com.xzzn.common.annotation.Log; +import com.xzzn.common.core.controller.BaseController; +import com.xzzn.common.core.domain.AjaxResult; +import com.xzzn.common.core.page.TableDataInfo; +import com.xzzn.common.enums.BusinessType; +import com.xzzn.ems.domain.EmsPointConfig; +import com.xzzn.ems.domain.vo.ImportPointTemplateRequest; +import com.xzzn.ems.domain.vo.PointConfigCurveRequest; +import com.xzzn.ems.domain.vo.PointConfigLatestValueRequest; +import com.xzzn.ems.service.IEmsPointConfigService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import javax.validation.Valid; +import java.util.List; + +@RestController +@RequestMapping("/ems/pointConfig") +public class EmsPointConfigController extends BaseController { + + @Autowired + private IEmsPointConfigService pointConfigService; + + @GetMapping("/list") + public TableDataInfo list(EmsPointConfig pointConfig) { + startPage(); + List list = pointConfigService.selectPointConfigList(pointConfig); + return getDataTable(list); + } + + @GetMapping("/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) { + return success(pointConfigService.selectPointConfigById(id)); + } + + @Log(title = "点位配置", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody EmsPointConfig pointConfig) { + return toAjax(pointConfigService.insertPointConfig(pointConfig, getUsername())); + } + + @Log(title = "点位配置", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody EmsPointConfig pointConfig) { + return toAjax(pointConfigService.updatePointConfig(pointConfig, getUsername())); + } + + @Log(title = "点位配置", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) { + return toAjax(pointConfigService.deletePointConfigByIds(ids)); + } + + @Log(title = "点位配置", businessType = BusinessType.IMPORT) + @PostMapping("/importTemplateBySite") + public AjaxResult importTemplateBySite(@Valid @RequestBody ImportPointTemplateRequest request) { + return success(pointConfigService.importTemplateBySite(request, getUsername())); + } + + @Log(title = "点位配置", businessType = BusinessType.IMPORT) + @PostMapping("/importCsv") + public AjaxResult importCsv(@RequestParam String siteId, + @RequestParam(required = false) Boolean overwrite, + @RequestParam("file") MultipartFile file) { + return success(pointConfigService.importCsvBySite(siteId, overwrite, file, getUsername())); + } + + @GetMapping("/registerAddress") + public AjaxResult getRegisterAddress(@RequestParam String siteId, + @RequestParam String deviceCategory, + @RequestParam String deviceId, + @RequestParam String dataKey) { + return success(pointConfigService.getRegisterAddress(siteId, deviceCategory, deviceId, dataKey)); + } + + @PostMapping("/latestValues") + public AjaxResult latestValues(@RequestBody PointConfigLatestValueRequest request) { + return success(pointConfigService.getLatestValues(request)); + } + + @PostMapping("/curve") + public AjaxResult curve(@RequestBody PointConfigCurveRequest request) { + return success(pointConfigService.getCurveData(request)); + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsPointConfig.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsPointConfig.java new file mode 100644 index 0000000..309c0ea --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsPointConfig.java @@ -0,0 +1,217 @@ +package com.xzzn.ems.domain; + +import com.xzzn.common.annotation.Excel; +import com.xzzn.common.core.domain.BaseEntity; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import java.math.BigDecimal; + +/** + * 点位配置对象 ems_point_config + */ +public class EmsPointConfig extends BaseEntity { + private static final long serialVersionUID = 1L; + + private Long id; + + @Excel(name = "站点ID") + private String siteId; + + @Excel(name = "设备类型") + private String deviceCategory; + + @Excel(name = "设备ID") + private String deviceId; + + @Excel(name = "点位名称") + private String pointName; + + @Excel(name = "数据键") + private String dataKey; + + @Excel(name = "点位描述") + private String pointDesc; + + @Excel(name = "寄存器地址") + private String registerAddress; + + @Excel(name = "单位") + private String dataUnit; + + @Excel(name = "A系数") + private BigDecimal dataA; + + @Excel(name = "K系数") + private BigDecimal dataK; + + @Excel(name = "B系数") + private BigDecimal dataB; + + @Excel(name = "位偏移") + private Integer dataBit; + + @Excel(name = "是否报警点位", readConverterExp = "0=否,1=是") + private Integer isAlarm; + + @Excel(name = "点位类型") + private String pointType; + + @Excel(name = "计算表达式") + private String calcExpression; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public String getDeviceCategory() { + return deviceCategory; + } + + public void setDeviceCategory(String deviceCategory) { + this.deviceCategory = deviceCategory; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getPointName() { + return pointName; + } + + public void setPointName(String pointName) { + this.pointName = pointName; + } + + public String getDataKey() { + return dataKey; + } + + public void setDataKey(String dataKey) { + this.dataKey = dataKey; + } + + public String getPointDesc() { + return pointDesc; + } + + public void setPointDesc(String pointDesc) { + this.pointDesc = pointDesc; + } + + public String getRegisterAddress() { + return registerAddress; + } + + public void setRegisterAddress(String registerAddress) { + this.registerAddress = registerAddress; + } + + public String getDataUnit() { + return dataUnit; + } + + public void setDataUnit(String dataUnit) { + this.dataUnit = dataUnit; + } + + public BigDecimal getDataA() { + return dataA; + } + + public void setDataA(BigDecimal dataA) { + this.dataA = dataA; + } + + public BigDecimal getDataK() { + return dataK; + } + + public void setDataK(BigDecimal dataK) { + this.dataK = dataK; + } + + public BigDecimal getDataB() { + return dataB; + } + + public void setDataB(BigDecimal dataB) { + this.dataB = dataB; + } + + public Integer getDataBit() { + return dataBit; + } + + public void setDataBit(Integer dataBit) { + this.dataBit = dataBit; + } + + public Integer getIsAlarm() { + return isAlarm; + } + + public void setIsAlarm(Integer isAlarm) { + this.isAlarm = isAlarm; + } + + public String getPointType() { + return pointType; + } + + public void setPointType(String pointType) { + this.pointType = pointType; + } + + public String getCalcExpression() { + return calcExpression; + } + + public void setCalcExpression(String calcExpression) { + this.calcExpression = calcExpression; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("siteId", getSiteId()) + .append("deviceCategory", getDeviceCategory()) + .append("deviceId", getDeviceId()) + .append("pointName", getPointName()) + .append("dataKey", getDataKey()) + .append("pointDesc", getPointDesc()) + .append("registerAddress", getRegisterAddress()) + .append("dataUnit", getDataUnit()) + .append("dataA", getDataA()) + .append("dataK", getDataK()) + .append("dataB", getDataB()) + .append("dataBit", getDataBit()) + .append("isAlarm", getIsAlarm()) + .append("pointType", getPointType()) + .append("calcExpression", getCalcExpression()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorData.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorData.java new file mode 100644 index 0000000..684b117 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorData.java @@ -0,0 +1,58 @@ +package com.xzzn.ems.domain; + +import com.xzzn.common.core.domain.BaseEntity; + +import java.util.Date; + +/** + * 单站监控展示数据对象(按分组落到 ems_site_monitor_data_home/sbjk/tjbb) + */ +public class EmsSiteMonitorData extends BaseEntity { + private static final long serialVersionUID = 1L; + + private Long id; + private String siteId; + private String fieldCode; + private String fieldValue; + private Date valueTime; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public String getFieldCode() { + return fieldCode; + } + + public void setFieldCode(String fieldCode) { + this.fieldCode = fieldCode; + } + + public String getFieldValue() { + return fieldValue; + } + + public void setFieldValue(String fieldValue) { + this.fieldValue = fieldValue; + } + + public Date getValueTime() { + return valueTime; + } + + public void setValueTime(Date valueTime) { + this.valueTime = valueTime; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorItem.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorItem.java new file mode 100644 index 0000000..84c8857 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorItem.java @@ -0,0 +1,101 @@ +package com.xzzn.ems.domain; + +import com.xzzn.common.core.domain.BaseEntity; + +/** + * 单站监控配置项定义对象 ems_site_monitor_item + */ +public class EmsSiteMonitorItem extends BaseEntity { + private static final long serialVersionUID = 1L; + + private Long id; + private String moduleCode; + private String moduleName; + private String menuCode; + private String menuName; + private String sectionName; + private String fieldCode; + private String fieldName; + private Integer sortNo; + private Integer status; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getModuleCode() { + return moduleCode; + } + + public void setModuleCode(String moduleCode) { + this.moduleCode = moduleCode; + } + + public String getModuleName() { + return moduleName; + } + + public void setModuleName(String moduleName) { + this.moduleName = moduleName; + } + + public String getMenuCode() { + return menuCode; + } + + public void setMenuCode(String menuCode) { + this.menuCode = menuCode; + } + + public String getMenuName() { + return menuName; + } + + public void setMenuName(String menuName) { + this.menuName = menuName; + } + + public String getSectionName() { + return sectionName; + } + + public void setSectionName(String sectionName) { + this.sectionName = sectionName; + } + + public String getFieldCode() { + return fieldCode; + } + + public void setFieldCode(String fieldCode) { + this.fieldCode = fieldCode; + } + + public String getFieldName() { + return fieldName; + } + + public void setFieldName(String fieldName) { + this.fieldName = fieldName; + } + + public Integer getSortNo() { + return sortNo; + } + + public void setSortNo(Integer sortNo) { + this.sortNo = sortNo; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorPointMatch.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorPointMatch.java new file mode 100644 index 0000000..e6db1d3 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorPointMatch.java @@ -0,0 +1,47 @@ +package com.xzzn.ems.domain; + +import com.xzzn.common.core.domain.BaseEntity; + +/** + * 单站监控字段点位映射对象 ems_site_monitor_point_match + */ +public class EmsSiteMonitorPointMatch extends BaseEntity { + private static final long serialVersionUID = 1L; + + private Long id; + private String siteId; + private String fieldCode; + private String dataPoint; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public String getFieldCode() { + return fieldCode; + } + + public void setFieldCode(String fieldCode) { + this.fieldCode = fieldCode; + } + + public String getDataPoint() { + return dataPoint; + } + + public void setDataPoint(String dataPoint) { + this.dataPoint = dataPoint; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/GeneralQueryPointOptionVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/GeneralQueryPointOptionVo.java new file mode 100644 index 0000000..d48520f --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/GeneralQueryPointOptionVo.java @@ -0,0 +1,31 @@ +package com.xzzn.ems.domain.vo; + +public class GeneralQueryPointOptionVo { + private String pointName; + private String dataKey; + private String pointDesc; + + public String getPointName() { + return pointName; + } + + public void setPointName(String pointName) { + this.pointName = pointName; + } + + public String getDataKey() { + return dataKey; + } + + public void setDataKey(String dataKey) { + this.dataKey = dataKey; + } + + public String getPointDesc() { + return pointDesc; + } + + public void setPointDesc(String pointDesc) { + this.pointDesc = pointDesc; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/ImportPointTemplateRequest.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/ImportPointTemplateRequest.java new file mode 100644 index 0000000..1ca6a31 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/ImportPointTemplateRequest.java @@ -0,0 +1,32 @@ +package com.xzzn.ems.domain.vo; + +import javax.validation.constraints.NotBlank; + +/** + * 按站点导入点位模板请求参数 + */ +public class ImportPointTemplateRequest { + + /** 目标站点ID */ + @NotBlank(message = "站点ID不能为空") + private String siteId; + + /** 是否覆盖目标站点已有点位配置 */ + private Boolean overwrite; + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public Boolean getOverwrite() { + return overwrite; + } + + public void setOverwrite(Boolean overwrite) { + this.overwrite = overwrite; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigCurveRequest.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigCurveRequest.java new file mode 100644 index 0000000..7777305 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigCurveRequest.java @@ -0,0 +1,58 @@ +package com.xzzn.ems.domain.vo; + +public class PointConfigCurveRequest { + private String siteId; + private String deviceId; + private String dataKey; + private String rangeType; + private String startTime; + private String endTime; + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getDataKey() { + return dataKey; + } + + public void setDataKey(String dataKey) { + this.dataKey = dataKey; + } + + public String getRangeType() { + return rangeType; + } + + public void setRangeType(String rangeType) { + this.rangeType = rangeType; + } + + public String getStartTime() { + return startTime; + } + + public void setStartTime(String startTime) { + this.startTime = startTime; + } + + public String getEndTime() { + return endTime; + } + + public void setEndTime(String endTime) { + this.endTime = endTime; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigCurveValueVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigCurveValueVo.java new file mode 100644 index 0000000..0ba51db --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigCurveValueVo.java @@ -0,0 +1,24 @@ +package com.xzzn.ems.domain.vo; + +import java.util.Date; + +public class PointConfigCurveValueVo { + private Date dataTime; + private Object pointValue; + + public Date getDataTime() { + return dataTime; + } + + public void setDataTime(Date dataTime) { + this.dataTime = dataTime; + } + + public Object getPointValue() { + return pointValue; + } + + public void setPointValue(Object pointValue) { + this.pointValue = pointValue; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigLatestValueItemVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigLatestValueItemVo.java new file mode 100644 index 0000000..4f8016d --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigLatestValueItemVo.java @@ -0,0 +1,31 @@ +package com.xzzn.ems.domain.vo; + +public class PointConfigLatestValueItemVo { + private String siteId; + private String deviceId; + private String dataKey; + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getDataKey() { + return dataKey; + } + + public void setDataKey(String dataKey) { + this.dataKey = dataKey; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigLatestValueRequest.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigLatestValueRequest.java new file mode 100644 index 0000000..37eb506 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigLatestValueRequest.java @@ -0,0 +1,15 @@ +package com.xzzn.ems.domain.vo; + +import java.util.List; + +public class PointConfigLatestValueRequest { + private List points; + + public List getPoints() { + return points; + } + + public void setPoints(List points) { + this.points = points; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigLatestValueVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigLatestValueVo.java new file mode 100644 index 0000000..76831c2 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigLatestValueVo.java @@ -0,0 +1,51 @@ +package com.xzzn.ems.domain.vo; + +import java.util.Date; + +public class PointConfigLatestValueVo { + private String siteId; + private String deviceId; + private String dataKey; + private Object pointValue; + private Date dataTime; + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getDataKey() { + return dataKey; + } + + public void setDataKey(String dataKey) { + this.dataKey = dataKey; + } + + public Object getPointValue() { + return pointValue; + } + + public void setPointValue(Object pointValue) { + this.pointValue = pointValue; + } + + public Date getDataTime() { + return dataTime; + } + + public void setDataTime(Date dataTime) { + this.dataTime = dataTime; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorDataSaveItemVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorDataSaveItemVo.java new file mode 100644 index 0000000..b71d607 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorDataSaveItemVo.java @@ -0,0 +1,33 @@ +package com.xzzn.ems.domain.vo; + +import java.util.Date; + +public class SiteMonitorDataSaveItemVo { + private String fieldCode; + private String fieldValue; + private Date valueTime; + + public String getFieldCode() { + return fieldCode; + } + + public void setFieldCode(String fieldCode) { + this.fieldCode = fieldCode; + } + + public String getFieldValue() { + return fieldValue; + } + + public void setFieldValue(String fieldValue) { + this.fieldValue = fieldValue; + } + + public Date getValueTime() { + return valueTime; + } + + public void setValueTime(Date valueTime) { + this.valueTime = valueTime; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorDataSaveRequest.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorDataSaveRequest.java new file mode 100644 index 0000000..252b4ed --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorDataSaveRequest.java @@ -0,0 +1,24 @@ +package com.xzzn.ems.domain.vo; + +import java.util.List; + +public class SiteMonitorDataSaveRequest { + private String siteId; + private List items; + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectDisplayVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectDisplayVo.java new file mode 100644 index 0000000..d1dd6a1 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectDisplayVo.java @@ -0,0 +1,27 @@ +package com.xzzn.ems.domain.vo; + +import java.util.Date; + +/** + * 单站监控展示数据(配置项 + 当前值) + */ +public class SiteMonitorProjectDisplayVo extends SiteMonitorProjectPointMappingVo { + private String fieldValue; + private Date valueTime; + + public String getFieldValue() { + return fieldValue; + } + + public void setFieldValue(String fieldValue) { + this.fieldValue = fieldValue; + } + + public Date getValueTime() { + return valueTime; + } + + public void setValueTime(Date valueTime) { + this.valueTime = valueTime; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingSaveRequest.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingSaveRequest.java new file mode 100644 index 0000000..c1df3ab --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingSaveRequest.java @@ -0,0 +1,26 @@ +package com.xzzn.ems.domain.vo; + +import java.util.List; + +public class SiteMonitorProjectPointMappingSaveRequest { + + private String siteId; + + private List mappings; + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public List getMappings() { + return mappings; + } + + public void setMappings(List mappings) { + this.mappings = mappings; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingVo.java new file mode 100644 index 0000000..3d518ed --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingVo.java @@ -0,0 +1,84 @@ +package com.xzzn.ems.domain.vo; + +public class SiteMonitorProjectPointMappingVo { + + private String moduleCode; + + private String moduleName; + + private String menuCode; + + private String menuName; + + private String sectionName; + + private String fieldCode; + + private String fieldName; + + private String dataPoint; + + public String getModuleCode() { + return moduleCode; + } + + public void setModuleCode(String moduleCode) { + this.moduleCode = moduleCode; + } + + public String getModuleName() { + return moduleName; + } + + public void setModuleName(String moduleName) { + this.moduleName = moduleName; + } + + public String getFieldCode() { + return fieldCode; + } + + public String getMenuCode() { + return menuCode; + } + + public void setMenuCode(String menuCode) { + this.menuCode = menuCode; + } + + public String getMenuName() { + return menuName; + } + + public void setMenuName(String menuName) { + this.menuName = menuName; + } + + public String getSectionName() { + return sectionName; + } + + public void setSectionName(String sectionName) { + this.sectionName = sectionName; + } + + public void setFieldCode(String fieldCode) { + this.fieldCode = fieldCode; + } + + public String getFieldName() { + return fieldName; + } + + public void setFieldName(String fieldName) { + this.fieldName = fieldName; + } + + public String getDataPoint() { + return dataPoint; + } + + public void setDataPoint(String dataPoint) { + this.dataPoint = dataPoint; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointConfigMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointConfigMapper.java new file mode 100644 index 0000000..76deb82 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointConfigMapper.java @@ -0,0 +1,44 @@ +package com.xzzn.ems.mapper; + +import com.xzzn.ems.domain.EmsPointConfig; +import com.xzzn.ems.domain.vo.GeneralQueryPointOptionVo; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface EmsPointConfigMapper { + EmsPointConfig selectEmsPointConfigById(Long id); + + List selectEmsPointConfigList(EmsPointConfig emsPointConfig); + + int insertEmsPointConfig(EmsPointConfig emsPointConfig); + + int updateEmsPointConfig(EmsPointConfig emsPointConfig); + + int deleteEmsPointConfigById(Long id); + + int deleteEmsPointConfigByIds(Long[] ids); + + int countBySiteId(@Param("siteId") String siteId); + + int deleteBySiteId(@Param("siteId") String siteId); + + int copyTemplateToSite(@Param("templateSiteId") String templateSiteId, + @Param("targetSiteId") String targetSiteId, + @Param("operName") String operName); + + String getRegisterAddress(@Param("siteId") String siteId, + @Param("deviceCategory") String deviceCategory, + @Param("deviceId") String deviceId, + @Param("dataKey") String dataKey); + + List getPointNameList(@Param("siteIds") List siteIds, + @Param("deviceCategory") String deviceCategory, + @Param("deviceId") String deviceId, + @Param("pointName") String pointName); + + List getConfigListForGeneralQuery(@Param("siteIds") List siteIds, + @Param("deviceCategory") String deviceCategory, + @Param("pointNames") List pointNames, + @Param("deviceIds") List deviceIds); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorDataMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorDataMapper.java new file mode 100644 index 0000000..7f55eff --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorDataMapper.java @@ -0,0 +1,28 @@ +package com.xzzn.ems.mapper; + +import org.apache.ibatis.annotations.Param; + +/** + * 单站监控展示数据 Mapper + */ +public interface EmsSiteMonitorDataMapper { + String selectHistoryJsonByMinute(@Param("tableName") String tableName, + @Param("siteId") String siteId, + @Param("statisMinute") java.util.Date statisMinute); + + int upsertHistoryJsonByMinute(@Param("tableName") String tableName, + @Param("siteId") String siteId, + @Param("statisMinute") java.util.Date statisMinute, + @Param("dataJson") String dataJson, + @Param("operName") String operName); + + int updateHistoryHotColumns(@Param("tableName") String tableName, + @Param("siteId") String siteId, + @Param("statisMinute") java.util.Date statisMinute, + @Param("hotSoc") String hotSoc, + @Param("hotTotalActivePower") String hotTotalActivePower, + @Param("hotTotalReactivePower") String hotTotalReactivePower, + @Param("hotDayChargedCap") String hotDayChargedCap, + @Param("hotDayDisChargedCap") String hotDayDisChargedCap, + @Param("operName") String operName); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorItemMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorItemMapper.java new file mode 100644 index 0000000..16b18c3 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorItemMapper.java @@ -0,0 +1,12 @@ +package com.xzzn.ems.mapper; + +import com.xzzn.ems.domain.EmsSiteMonitorItem; + +import java.util.List; + +/** + * 单站监控配置项定义 Mapper + */ +public interface EmsSiteMonitorItemMapper { + List selectEnabledList(); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorPointMatchMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorPointMatchMapper.java new file mode 100644 index 0000000..0f001d6 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorPointMatchMapper.java @@ -0,0 +1,17 @@ +package com.xzzn.ems.mapper; + +import com.xzzn.ems.domain.EmsSiteMonitorPointMatch; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 单站监控字段点位映射 Mapper + */ +public interface EmsSiteMonitorPointMatchMapper { + List selectBySiteId(@Param("siteId") String siteId); + + int deleteBySiteId(@Param("siteId") String siteId); + + int insertBatch(@Param("list") List list); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/IEmsPointConfigService.java b/ems-system/src/main/java/com/xzzn/ems/service/IEmsPointConfigService.java new file mode 100644 index 0000000..04abb2a --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/IEmsPointConfigService.java @@ -0,0 +1,33 @@ +package com.xzzn.ems.service; + +import com.xzzn.ems.domain.EmsPointConfig; +import com.xzzn.ems.domain.vo.ImportPointTemplateRequest; +import com.xzzn.ems.domain.vo.PointConfigCurveRequest; +import com.xzzn.ems.domain.vo.PointConfigCurveValueVo; +import com.xzzn.ems.domain.vo.PointConfigLatestValueRequest; +import com.xzzn.ems.domain.vo.PointConfigLatestValueVo; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +public interface IEmsPointConfigService { + List selectPointConfigList(EmsPointConfig pointConfig); + + EmsPointConfig selectPointConfigById(Long id); + + int insertPointConfig(EmsPointConfig pointConfig, String operName); + + int updatePointConfig(EmsPointConfig pointConfig, String operName); + + int deletePointConfigByIds(Long[] ids); + + String importTemplateBySite(ImportPointTemplateRequest request, String operName); + + String importCsvBySite(String siteId, Boolean overwrite, MultipartFile file, String operName); + + String getRegisterAddress(String siteId, String deviceCategory, String deviceId, String dataKey); + + List getLatestValues(PointConfigLatestValueRequest request); + + List getCurveData(PointConfigCurveRequest request); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/InfluxPointDataWriter.java b/ems-system/src/main/java/com/xzzn/ems/service/InfluxPointDataWriter.java new file mode 100644 index 0000000..0799678 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/InfluxPointDataWriter.java @@ -0,0 +1,616 @@ +package com.xzzn.ems.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.math.BigDecimal; +import java.net.HttpURLConnection; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.HashMap; +import java.util.Map; + +@Component +public class InfluxPointDataWriter { + + private static final Logger log = LoggerFactory.getLogger(InfluxPointDataWriter.class); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + @Value("${influxdb.enabled:true}") + private boolean enabled; + + @Value("${influxdb.url:}") + private String url; + + @Value("${influxdb.username:}") + private String username; + + @Value("${influxdb.password:}") + private String password; + + @Value("${influxdb.api-token:}") + private String apiToken; + + @Value("${influxdb.database:ems_point_data}") + private String database; + + @Value("${influxdb.retention-policy:autogen}") + private String retentionPolicy; + + @Value("${influxdb.measurement:mqtt_point_data}") + private String measurement; + + @Value("${influxdb.write-method:POST}") + private String writeMethod; + + @Value("${influxdb.read-method:GET}") + private String readMethod; + + @Value("${influxdb.write-path:/write}") + private String writePath; + + @Value("${influxdb.query-path:/query}") + private String queryPath; + + @Value("${influxdb.org:}") + private String org; + + @Value("${influxdb.bucket:}") + private String bucket; + + @PostConstruct + public void init() { + if (!enabled) { + log.info("InfluxDB 写入已禁用"); + return; + } + if (url == null || url.trim().isEmpty()) { + log.warn("InfluxDB URL 未配置,跳过初始化"); + return; + } + log.info("InfluxDB 已启用 HTTP 接入, url: {}, database: {}", url, database); + } + + public void writeBatch(List payloads) { + if (!enabled || payloads == null || payloads.isEmpty()) { + return; + } + try { + StringBuilder body = new StringBuilder(); + for (PointWritePayload payload : payloads) { + if (payload == null || payload.getPointValue() == null) { + continue; + } + long time = payload.getDataTime() == null ? System.currentTimeMillis() : payload.getDataTime().getTime(); + body.append(measurement) + .append(",site_id=").append(escapeLineTag(payload.getSiteId())) + .append(",device_id=").append(escapeLineTag(payload.getDeviceId())) + .append(",point_key=").append(escapeLineTag(payload.getPointKey())) + .append(" value=").append(payload.getPointValue().toPlainString()) + .append(" ").append(time) + .append("\n"); + } + if (body.length() == 0) { + return; + } + String writeUrl = buildWriteUrl(); + if (isBlank(writeUrl)) { + log.warn("写入 InfluxDB 失败:v2 写入地址未构建成功,请检查 influxdb.org / influxdb.bucket 配置"); + return; + } + HttpResult result = executeRequest(methodOrDefault(writeMethod, "POST"), writeUrl, body.toString()); + if (result.code < 200 || result.code >= 300) { + if (result.code == 404 && isV2WritePath() && isOrgOrBucketMissing(result.body)) { + if (ensureV2OrgAndBucket()) { + HttpResult retryResult = executeRequest(methodOrDefault(writeMethod, "POST"), writeUrl, body.toString()); + if (retryResult.code >= 200 && retryResult.code < 300) { + log.info("InfluxDB org/bucket 自动创建成功,写入已恢复"); + return; + } + log.warn("InfluxDB 重试写入失败,HTTP状态码: {}, url: {}, body: {}", retryResult.code, writeUrl, safeLog(retryResult.body)); + return; + } + } + log.warn("写入 InfluxDB 失败,HTTP状态码: {}, url: {}, body: {}", result.code, writeUrl, safeLog(result.body)); + } + } catch (Exception e) { + log.warn("写入 InfluxDB 失败: {}", e.getMessage()); + } + } + + public List queryCurveData(String siteId, String deviceId, String pointKey, Date startTime, Date endTime) { + if (!enabled) { + return Collections.emptyList(); + } + if (isBlank(siteId) || isBlank(deviceId) || isBlank(pointKey) || startTime == null || endTime == null) { + return Collections.emptyList(); + } + + String normalizedSiteId = siteId.trim(); + String normalizedDeviceId = deviceId.trim(); + String normalizedPointKey = pointKey.trim(); + + String influxQl = String.format( + "SELECT \"value\" FROM \"%s\" WHERE \"site_id\" = '%s' AND \"device_id\" = '%s' AND \"point_key\" = '%s' " + + "AND time >= %dms AND time <= %dms ORDER BY time ASC", + measurement, + escapeTagValue(normalizedSiteId), + escapeTagValue(normalizedDeviceId), + escapeTagValue(normalizedPointKey), + startTime.getTime(), + endTime.getTime() + ); + + try { + String queryUrl = buildQueryUrl(influxQl); + List values = parseInfluxQlResponse(executeRequestWithResponse(methodOrDefault(readMethod, "GET"), queryUrl)); + if (!values.isEmpty()) { + return values; + } + + // 兼容 dataKey 大小写差异,避免点位 key 字母大小写不一致导致查不到曲线 + String regexQuery = String.format( + "SELECT \"value\" FROM \"%s\" WHERE \"site_id\" = '%s' AND \"device_id\" = '%s' AND \"point_key\" =~ /(?i)^%s$/ " + + "AND time >= %dms AND time <= %dms ORDER BY time ASC", + measurement, + escapeTagValue(normalizedSiteId), + escapeTagValue(normalizedDeviceId), + escapeRegex(normalizedPointKey), + startTime.getTime(), + endTime.getTime() + ); + values = parseInfluxQlResponse(executeRequestWithResponse(methodOrDefault(readMethod, "GET"), buildQueryUrl(regexQuery))); + return values; + } catch (Exception e) { + log.warn("查询 InfluxDB 曲线失败: {}", e.getMessage()); + return Collections.emptyList(); + } + } + + private String buildWriteUrl() { + if (isV2WritePath()) { + return buildV2WriteUrl(); + } + StringBuilder sb = new StringBuilder(trimTrailingSlash(url)); + sb.append(normalizePath(writePath)).append("?db=").append(urlEncode(database)); + if (!isBlank(retentionPolicy)) { + sb.append("&rp=").append(urlEncode(retentionPolicy)); + } + sb.append("&precision=ms"); + return sb.toString(); + } + + private String buildQueryUrl(String influxQl) { + String queryDb = database; + if (isV2WritePath() && !isBlank(bucket)) { + queryDb = bucket; + } + StringBuilder queryUrl = new StringBuilder(trimTrailingSlash(url)) + .append(normalizePath(queryPath)) + .append("?db=").append(urlEncode(queryDb)) + .append("&epoch=ms&q=").append(urlEncode(influxQl)); + if (!isBlank(retentionPolicy)) { + queryUrl.append("&rp=").append(urlEncode(retentionPolicy)); + } + return queryUrl.toString(); + } + + private String buildV2WriteUrl() { + String currentBucket = isBlank(bucket) ? database : bucket; + if (isBlank(org) || isBlank(currentBucket)) { + return null; + } + return trimTrailingSlash(url) + + "/api/v2/write?org=" + urlEncode(org) + + "&bucket=" + urlEncode(currentBucket) + + "&precision=ms"; + } + + private HttpResult executeRequest(String method, String requestUrl, String body) throws Exception { + HttpURLConnection connection = openConnection(method, requestUrl, "text/plain; charset=UTF-8"); + try { + if (body != null) { + connection.setDoOutput(true); + try (OutputStream os = connection.getOutputStream()) { + os.write(body.getBytes(StandardCharsets.UTF_8)); + } + } + int code = connection.getResponseCode(); + InputStream is = (code >= 200 && code < 300) ? connection.getInputStream() : connection.getErrorStream(); + return new HttpResult(code, readStream(is)); + } finally { + connection.disconnect(); + } + } + + private String executeRequestWithResponse(String method, String requestUrl) throws Exception { + HttpURLConnection connection = openConnection(method, requestUrl, "text/plain; charset=UTF-8"); + try { + int code = connection.getResponseCode(); + InputStream is = (code >= 200 && code < 300) ? connection.getInputStream() : connection.getErrorStream(); + return readStream(is); + } finally { + connection.disconnect(); + } + } + + private String readStream(InputStream is) throws Exception { + if (is == null) { + return ""; + } + try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + return sb.toString(); + } + } + + private HttpResult executeJsonRequest(String method, String requestUrl, String jsonBody) throws Exception { + HttpURLConnection connection = openConnection(method, requestUrl, "application/json; charset=UTF-8"); + try { + if (jsonBody != null) { + connection.setDoOutput(true); + try (OutputStream os = connection.getOutputStream()) { + os.write(jsonBody.getBytes(StandardCharsets.UTF_8)); + } + } + int code = connection.getResponseCode(); + InputStream is = (code >= 200 && code < 300) ? connection.getInputStream() : connection.getErrorStream(); + return new HttpResult(code, readStream(is)); + } finally { + connection.disconnect(); + } + } + + private HttpURLConnection openConnection(String method, String requestUrl, String contentType) throws Exception { + HttpURLConnection connection = (HttpURLConnection) new java.net.URL(requestUrl).openConnection(); + connection.setRequestMethod(method); + connection.setConnectTimeout(5000); + connection.setReadTimeout(8000); + connection.setRequestProperty("Accept", "application/json"); + connection.setRequestProperty("Content-Type", contentType); + if (!isBlank(apiToken)) { + connection.setRequestProperty("Authorization", "Token " + apiToken.trim()); + } else if (!isBlank(username) || !isBlank(password)) { + String basic = java.util.Base64.getEncoder() + .encodeToString((safe(username) + ":" + safe(password)).getBytes(StandardCharsets.UTF_8)); + connection.setRequestProperty("Authorization", "Basic " + basic); + } + return connection; + } + + private String escapeLineTag(String value) { + if (value == null) { + return ""; + } + return value.replace("\\", "\\\\") + .replace(",", "\\,") + .replace(" ", "\\ ") + .replace("=", "\\="); + } + + private String trimTrailingSlash(String v) { + if (v == null) { + return ""; + } + String result = v.trim(); + while (result.endsWith("/")) { + result = result.substring(0, result.length() - 1); + } + return result; + } + + private String urlEncode(String value) { + try { + return URLEncoder.encode(safe(value), StandardCharsets.UTF_8.name()); + } catch (Exception e) { + return safe(value); + } + } + + private String safe(String value) { + return value == null ? "" : value; + } + + private boolean isV2WritePath() { + return "/api/v2/write".equals(normalizePath(writePath)); + } + + private boolean isOrgOrBucketMissing(String responseBody) { + if (isBlank(responseBody)) { + return false; + } + String lower = responseBody.toLowerCase(); + return (lower.contains("organization") && lower.contains("not found")) + || (lower.contains("bucket") && lower.contains("not found")); + } + + private boolean ensureV2OrgAndBucket() { + try { + if (isBlank(org)) { + log.warn("InfluxDB 自动创建 organization 失败:org 配置为空"); + return false; + } + String currentBucket = isBlank(bucket) ? database : bucket; + String orgId = queryOrgId(org); + if (isBlank(orgId)) { + orgId = createOrg(org); + } + if (isBlank(orgId)) { + log.warn("InfluxDB 自动创建 organization 失败,org={}", org); + return false; + } + + if (!bucketExists(org, currentBucket)) { + if (!createBucket(orgId, currentBucket)) { + log.warn("InfluxDB 自动创建 bucket 失败,org={}, bucket={}", org, currentBucket); + return false; + } + } + return true; + } catch (Exception ex) { + log.warn("InfluxDB 自动创建 org/bucket 异常: {}", ex.getMessage()); + return false; + } + } + + private String queryOrgId(String orgName) throws Exception { + String requestUrl = trimTrailingSlash(url) + "/api/v2/orgs?org=" + urlEncode(orgName); + HttpResult result = executeJsonRequest("GET", requestUrl, null); + if (result.code < 200 || result.code >= 300 || isBlank(result.body)) { + log.warn("查询 organization 失败,status={}, org={}, body={}", result.code, orgName, safeLog(result.body)); + return null; + } + JsonNode root = OBJECT_MAPPER.readTree(result.body); + JsonNode orgs = root.path("orgs"); + if (orgs.isArray() && orgs.size() > 0) { + JsonNode first = orgs.get(0); + if (first != null) { + String id = first.path("id").asText(null); + if (!isBlank(id)) { + return id; + } + } + } + return null; + } + + private String createOrg(String orgName) throws Exception { + String requestUrl = trimTrailingSlash(url) + "/api/v2/orgs"; + Map payload = new HashMap<>(); + payload.put("name", orgName); + String body = OBJECT_MAPPER.writeValueAsString(payload); + HttpResult result = executeJsonRequest("POST", requestUrl, body); + if ((result.code < 200 || result.code >= 300) || isBlank(result.body)) { + log.warn("创建 organization 失败,status={}, org={}, body={}", result.code, orgName, safeLog(result.body)); + return null; + } + JsonNode root = OBJECT_MAPPER.readTree(result.body); + String id = root.path("id").asText(null); + return isBlank(id) ? null : id; + } + + private boolean bucketExists(String orgName, String bucketName) throws Exception { + String requestUrl = trimTrailingSlash(url) + + "/api/v2/buckets?name=" + urlEncode(bucketName) + + "&org=" + urlEncode(orgName); + HttpResult result = executeJsonRequest("GET", requestUrl, null); + if (result.code < 200 || result.code >= 300 || isBlank(result.body)) { + log.warn("查询 bucket 失败,status={}, org={}, bucket={}, body={}", result.code, orgName, bucketName, safeLog(result.body)); + return false; + } + JsonNode root = OBJECT_MAPPER.readTree(result.body); + JsonNode buckets = root.path("buckets"); + return buckets.isArray() && buckets.size() > 0; + } + + private boolean createBucket(String orgId, String bucketName) throws Exception { + String requestUrl = trimTrailingSlash(url) + "/api/v2/buckets"; + Map payload = new HashMap<>(); + payload.put("orgID", orgId); + payload.put("name", bucketName); + String body = OBJECT_MAPPER.writeValueAsString(payload); + HttpResult result = executeJsonRequest("POST", requestUrl, body); + if (result.code < 200 || result.code >= 300) { + log.warn("创建 bucket 失败,status={}, orgId={}, bucket={}, body={}", result.code, orgId, bucketName, safeLog(result.body)); + } + return result.code >= 200 && result.code < 300; + } + + private String methodOrDefault(String method, String defaultMethod) { + return isBlank(method) ? defaultMethod : method.trim().toUpperCase(); + } + + private String normalizePath(String path) { + if (isBlank(path)) { + return "/"; + } + String p = path.trim(); + return p.startsWith("/") ? p : "/" + p; + } + + private String safeLog(String body) { + if (body == null) { + return ""; + } + return body.length() > 200 ? body.substring(0, 200) : body; + } + + private Date parseInfluxTime(Object timeObject) { + if (timeObject == null) { + return null; + } + if (timeObject instanceof Number) { + return new Date(((Number) timeObject).longValue()); + } + try { + return Date.from(Instant.parse(String.valueOf(timeObject))); + } catch (Exception e) { + return null; + } + } + + private BigDecimal toBigDecimal(Object valueObject) { + if (valueObject == null) { + return null; + } + if (valueObject instanceof BigDecimal) { + return (BigDecimal) valueObject; + } + if (valueObject instanceof Number) { + return new BigDecimal(valueObject.toString()); + } + try { + return new BigDecimal(String.valueOf(valueObject)); + } catch (Exception e) { + return null; + } + } + + private String escapeTagValue(String value) { + return value == null ? "" : value.replace("\\", "\\\\").replace("'", "\\'"); + } + + private String escapeRegex(String value) { + if (value == null) { + return ""; + } + return value.replace("\\", "\\\\") + .replace("/", "\\/") + .replace(".", "\\.") + .replace("(", "\\(") + .replace(")", "\\)") + .replace("[", "\\[") + .replace("]", "\\]") + .replace("{", "\\{") + .replace("}", "\\}") + .replace("^", "\\^") + .replace("$", "\\$") + .replace("*", "\\*") + .replace("+", "\\+") + .replace("?", "\\?") + .replace("|", "\\|"); + } + + private List parseInfluxQlResponse(String response) throws Exception { + if (isBlank(response)) { + return Collections.emptyList(); + } + List values = new ArrayList<>(); + JsonNode root = OBJECT_MAPPER.readTree(response); + JsonNode resultsNode = root.path("results"); + if (!resultsNode.isArray()) { + return values; + } + + for (JsonNode result : resultsNode) { + JsonNode seriesArray = result.path("series"); + if (!seriesArray.isArray()) { + continue; + } + for (JsonNode series : seriesArray) { + JsonNode rows = series.path("values"); + if (!rows.isArray()) { + continue; + } + for (JsonNode row : rows) { + if (!row.isArray() || row.size() < 2) { + continue; + } + Date dataTime = parseInfluxTime(row.get(0).isNumber() ? row.get(0).asLong() : row.get(0).asText()); + BigDecimal pointValue = toBigDecimal(row.get(1).isNumber() ? row.get(1).asText() : row.get(1).asText(null)); + if (dataTime == null || pointValue == null) { + continue; + } + values.add(new PointValue(dataTime, pointValue)); + } + } + } + return values; + } + + private boolean isBlank(String value) { + return value == null || value.trim().isEmpty(); + } + + public static class PointWritePayload { + private final String siteId; + private final String deviceId; + private final String pointKey; + private final BigDecimal pointValue; + private final Date dataTime; + + public PointWritePayload(String siteId, String deviceId, String pointKey, BigDecimal pointValue, Date dataTime) { + this.siteId = siteId; + this.deviceId = deviceId; + this.pointKey = pointKey; + this.pointValue = pointValue; + this.dataTime = dataTime; + } + + public String getSiteId() { + return siteId; + } + + public String getDeviceId() { + return deviceId; + } + + public String getPointKey() { + return pointKey; + } + + public BigDecimal getPointValue() { + return pointValue; + } + + public Date getDataTime() { + return dataTime; + } + } + + public static class PointValue { + private final Date dataTime; + private final BigDecimal pointValue; + + public PointValue(Date dataTime, BigDecimal pointValue) { + this.dataTime = dataTime; + this.pointValue = pointValue; + } + + public Date getDataTime() { + return dataTime; + } + + public BigDecimal getPointValue() { + return pointValue; + } + } + + private static class HttpResult { + private final int code; + private final String body; + + private HttpResult(int code, String body) { + this.code = code; + this.body = body; + } + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointConfigServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointConfigServiceImpl.java new file mode 100644 index 0000000..d39dd20 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointConfigServiceImpl.java @@ -0,0 +1,668 @@ +package com.xzzn.ems.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.xzzn.common.exception.ServiceException; +import com.xzzn.common.constant.RedisKeyConstants; +import com.xzzn.common.core.redis.RedisCache; +import com.xzzn.common.utils.DateUtils; +import com.xzzn.common.utils.StringUtils; +import com.xzzn.ems.domain.EmsPointConfig; +import com.xzzn.ems.domain.vo.ImportPointTemplateRequest; +import com.xzzn.ems.domain.vo.PointConfigCurveRequest; +import com.xzzn.ems.domain.vo.PointConfigCurveValueVo; +import com.xzzn.ems.domain.vo.PointConfigLatestValueItemVo; +import com.xzzn.ems.domain.vo.PointConfigLatestValueRequest; +import com.xzzn.ems.domain.vo.PointConfigLatestValueVo; +import com.xzzn.ems.mapper.EmsPointConfigMapper; +import com.xzzn.ems.service.IEmsPointConfigService; +import com.xzzn.ems.service.InfluxPointDataWriter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Locale; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +@Service +public class EmsPointConfigServiceImpl implements IEmsPointConfigService { + private static final String TEMPLATE_SITE_ID = "DEFAULT"; + private static final Pattern DB_NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]+$"); + private static final Pattern INTEGER_PATTERN = Pattern.compile("^-?\\d+$"); + private static final Pattern DECIMAL_PATTERN = Pattern.compile("^-?\\d+(\\.\\d+)?$"); + private static final Pattern CALC_EXPRESSION_PATTERN = Pattern.compile("^[0-9A-Za-z_+\\-*/().\\s]+$"); + private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + @Autowired + private EmsPointConfigMapper emsPointConfigMapper; + @Autowired + private RedisCache redisCache; + @Autowired + private InfluxPointDataWriter influxPointDataWriter; + + @Override + public List selectPointConfigList(EmsPointConfig pointConfig) { + return emsPointConfigMapper.selectEmsPointConfigList(pointConfig); + } + + @Override + public EmsPointConfig selectPointConfigById(Long id) { + return emsPointConfigMapper.selectEmsPointConfigById(id); + } + + @Override + public int insertPointConfig(EmsPointConfig pointConfig, String operName) { + if (pointConfig == null || StringUtils.isBlank(pointConfig.getSiteId())) { + throw new ServiceException("站点ID不能为空"); + } + normalizeAndValidatePointConfig(pointConfig); + pointConfig.setCreateBy(operName); + pointConfig.setUpdateBy(operName); + int rows = emsPointConfigMapper.insertEmsPointConfig(pointConfig); + if (rows > 0) { + invalidatePointConfigCache(pointConfig.getSiteId(), pointConfig.getDeviceId()); + } + return rows; + } + + @Override + public int updatePointConfig(EmsPointConfig pointConfig, String operName) { + EmsPointConfig oldConfig = pointConfig.getId() == null ? null : emsPointConfigMapper.selectEmsPointConfigById(pointConfig.getId()); + normalizeAndValidatePointConfig(pointConfig); + pointConfig.setUpdateBy(operName); + int rows = emsPointConfigMapper.updateEmsPointConfig(pointConfig); + if (rows > 0) { + if (oldConfig != null) { + invalidatePointConfigCache(oldConfig.getSiteId(), oldConfig.getDeviceId()); + } + invalidatePointConfigCache(pointConfig.getSiteId(), pointConfig.getDeviceId()); + } + return rows; + } + + @Override + public int deletePointConfigByIds(Long[] ids) { + if (ids != null) { + for (Long id : ids) { + EmsPointConfig oldConfig = emsPointConfigMapper.selectEmsPointConfigById(id); + if (oldConfig != null) { + invalidatePointConfigCache(oldConfig.getSiteId(), oldConfig.getDeviceId()); + } + } + } + return emsPointConfigMapper.deleteEmsPointConfigByIds(ids); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String importTemplateBySite(ImportPointTemplateRequest request, String operName) { + String targetSiteId = request.getSiteId(); + if (StringUtils.isBlank(targetSiteId)) { + throw new ServiceException("站点ID不能为空"); + } + if (TEMPLATE_SITE_ID.equals(targetSiteId)) { + throw new ServiceException("模板站点不支持作为导入目标站点"); + } + + int templateCount = emsPointConfigMapper.countBySiteId(TEMPLATE_SITE_ID); + if (templateCount <= 0) { + throw new ServiceException("模板点位配置不存在,无法导入"); + } + + boolean overwrite = Boolean.TRUE.equals(request.getOverwrite()); + int targetCount = emsPointConfigMapper.countBySiteId(targetSiteId); + if (targetCount > 0 && !overwrite) { + throw new ServiceException("目标站点已存在点位配置,请勾选“覆盖已存在数据”后重试"); + } + + if (targetCount > 0) { + emsPointConfigMapper.deleteBySiteId(targetSiteId); + } + + int importCount = emsPointConfigMapper.copyTemplateToSite(TEMPLATE_SITE_ID, targetSiteId, operName); + invalidatePointConfigCacheBySite(targetSiteId); + return String.format("导入成功:站点 %s,点位 %d 条", targetSiteId, importCount); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String importCsvBySite(String siteId, Boolean overwrite, MultipartFile file, String operName) { + if (StringUtils.isBlank(siteId)) { + throw new ServiceException("站点ID不能为空"); + } + if (!DB_NAME_PATTERN.matcher(siteId).matches()) { + throw new ServiceException(String.format("站点ID不合法(siteId=%s)", siteId)); + } + if (TEMPLATE_SITE_ID.equals(siteId)) { + throw new ServiceException("模板站点不支持作为导入目标站点"); + } + if (file == null || file.isEmpty()) { + throw new ServiceException("请上传CSV文件"); + } + if (!StringUtils.endsWithIgnoreCase(file.getOriginalFilename(), ".csv")) { + throw new ServiceException("仅支持上传CSV文件"); + } + + boolean overwriteFlag = Boolean.TRUE.equals(overwrite); + int targetCount = emsPointConfigMapper.countBySiteId(siteId); + if (targetCount > 0 && !overwriteFlag) { + throw new ServiceException("目标站点已存在点位配置,请勾选“覆盖已存在点位数据”后重试"); + } + + List pointConfigList = parseCsv(file, siteId); + if (pointConfigList.isEmpty()) { + throw new ServiceException("CSV没有可导入的数据"); + } + + if (targetCount > 0) { + emsPointConfigMapper.deleteBySiteId(siteId); + } + + int importCount = 0; + for (EmsPointConfig pointConfig : pointConfigList) { + pointConfig.setCreateBy(operName); + pointConfig.setUpdateBy(operName); + importCount += emsPointConfigMapper.insertEmsPointConfig(pointConfig); + } + + invalidatePointConfigCacheBySite(siteId); + return String.format("导入成功:站点 %s,点位 %d 条", siteId, importCount); + } + + @Override + public String getRegisterAddress(String siteId, String deviceCategory, String deviceId, String dataKey) { + if (StringUtils.isAnyBlank(siteId, deviceCategory, deviceId, dataKey)) { + return null; + } + return emsPointConfigMapper.getRegisterAddress(siteId, deviceCategory, deviceId, dataKey); + } + + @Override + public List getLatestValues(PointConfigLatestValueRequest request) { + List result = new ArrayList<>(); + if (request == null || request.getPoints() == null || request.getPoints().isEmpty()) { + return result; + } + + Map> configMapCache = new HashMap<>(); + for (PointConfigLatestValueItemVo item : request.getPoints()) { + if (item == null || StringUtils.isAnyBlank(item.getSiteId(), item.getDeviceId(), item.getDataKey())) { + continue; + } + PointConfigLatestValueVo latestValue = queryLatestValueFromRedis(item, configMapCache); + result.add(latestValue); + } + return result; + } + + @Override + public List getCurveData(PointConfigCurveRequest request) { + if (request == null || StringUtils.isAnyBlank(request.getSiteId(), request.getDeviceId(), request.getDataKey())) { + return new ArrayList<>(); + } + String siteId = StringUtils.trim(request.getSiteId()); + String deviceId = StringUtils.trim(request.getDeviceId()); + String dataKey = StringUtils.trim(request.getDataKey()); + if (StringUtils.isAnyBlank(siteId, deviceId, dataKey)) { + return new ArrayList<>(); + } + Date[] range = resolveTimeRange(request); + return queryCurveDataFromInflux(siteId, deviceId, dataKey, range[0], range[1]); + } + + private PointConfigLatestValueVo queryLatestValueFromRedis(PointConfigLatestValueItemVo item, + Map> configMapCache) { + PointConfigLatestValueVo vo = new PointConfigLatestValueVo(); + vo.setSiteId(item.getSiteId()); + vo.setDeviceId(item.getDeviceId()); + vo.setDataKey(item.getDataKey()); + + String redisKey = RedisKeyConstants.ORIGINAL_MQTT_DATA + item.getSiteId() + "_" + item.getDeviceId(); + Object raw = redisCache.getCacheObject(redisKey); + if (raw == null) { + return vo; + } + + JSONObject root = toJsonObject(raw); + if (root == null) { + return vo; + } + JSONObject dataObject = extractDataObject(root); + if (dataObject == null) { + return vo; + } + + Object rawValue = getValueIgnoreCase(dataObject, item.getDataKey()); + BigDecimal pointValue = StringUtils.getBigDecimal(rawValue); + if (pointValue != null) { + EmsPointConfig pointConfig = getPointConfig(item.getSiteId(), item.getDeviceId(), item.getDataKey(), configMapCache); + vo.setPointValue(convertPointValue(pointValue, pointConfig)); + } + vo.setDataTime(extractDataTime(root)); + return vo; + } + + private List queryCurveDataFromInflux(String siteId, String deviceId, String dataKey, Date startTime, Date endTime) { + List values = influxPointDataWriter.queryCurveData(siteId, deviceId, dataKey, startTime, endTime); + if (values == null || values.isEmpty()) { + return new ArrayList<>(); + } + return values.stream().map(value -> { + PointConfigCurveValueVo vo = new PointConfigCurveValueVo(); + vo.setDataTime(value.getDataTime()); + vo.setPointValue(value.getPointValue()); + return vo; + }).collect(Collectors.toList()); + } + + private Date[] resolveTimeRange(PointConfigCurveRequest request) { + LocalDateTime end = LocalDateTime.now(); + String rangeType = StringUtils.defaultIfBlank(request.getRangeType(), "day").toLowerCase(Locale.ROOT); + LocalDateTime start; + if ("custom".equals(rangeType)) { + if (StringUtils.isAnyBlank(request.getStartTime(), request.getEndTime())) { + throw new ServiceException("自定义时间范围必须传入开始时间和结束时间"); + } + start = parseDateTime(request.getStartTime()); + end = parseDateTime(request.getEndTime()); + } else if ("week".equals(rangeType)) { + start = end.minusDays(7); + } else if ("month".equals(rangeType)) { + start = end.minusDays(30); + } else { + start = end.minusDays(1); + } + if (start.isAfter(end)) { + throw new ServiceException("开始时间不能晚于结束时间"); + } + return new Date[]{ + Timestamp.valueOf(start), + Timestamp.valueOf(end) + }; + } + + private LocalDateTime parseDateTime(String value) { + try { + return LocalDateTime.parse(value, DATETIME_FORMATTER); + } catch (DateTimeParseException ex) { + throw new ServiceException("时间格式错误,请使用 yyyy-MM-dd HH:mm:ss"); + } + } + + private void invalidatePointConfigCache(String siteId, String deviceId) { + if (StringUtils.isAnyBlank(siteId, deviceId)) { + return; + } + redisCache.deleteObject(RedisKeyConstants.POINT_CONFIG_DEVICE + siteId + "_" + deviceId); + } + + private void invalidatePointConfigCacheBySite(String siteId) { + if (StringUtils.isBlank(siteId)) { + return; + } + Collection keys = redisCache.keys(RedisKeyConstants.POINT_CONFIG_DEVICE + siteId + "_*"); + if (keys != null && !keys.isEmpty()) { + redisCache.deleteObject(keys); + } + } + + private JSONObject toJsonObject(Object raw) { + if (raw == null) { + return null; + } + if (raw instanceof JSONObject) { + return (JSONObject) raw; + } + try { + return JSON.parseObject(JSON.toJSONString(raw)); + } catch (Exception ex) { + return null; + } + } + + private JSONObject extractDataObject(JSONObject root) { + if (root == null) { + return null; + } + JSONObject dataObject = root.getJSONObject("Data"); + if (dataObject != null) { + return dataObject; + } + String dataJson = root.getString("Data"); + if (StringUtils.isBlank(dataJson)) { + return null; + } + try { + return JSON.parseObject(dataJson); + } catch (Exception ex) { + return null; + } + } + + private Date extractDataTime(JSONObject root) { + if (root == null) { + return null; + } + Long timestamp = root.getLong("timestamp"); + if (timestamp != null) { + return DateUtils.convertUpdateTime(timestamp); + } + return null; + } + + private Object getValueIgnoreCase(JSONObject dataObject, String dataKey) { + if (dataObject == null || StringUtils.isBlank(dataKey)) { + return null; + } + Object directValue = dataObject.get(dataKey); + if (directValue != null) { + return directValue; + } + for (String key : dataObject.keySet()) { + if (key != null && key.equalsIgnoreCase(dataKey)) { + return dataObject.get(key); + } + } + return null; + } + + private EmsPointConfig getPointConfig(String siteId, + String deviceId, + String dataKey, + Map> configMapCache) { + if (StringUtils.isAnyBlank(siteId, deviceId, dataKey)) { + return null; + } + String cacheKey = siteId + "|" + deviceId; + Map configByKey = configMapCache.get(cacheKey); + if (configByKey == null) { + EmsPointConfig query = new EmsPointConfig(); + query.setSiteId(siteId); + query.setDeviceId(deviceId); + List configList = emsPointConfigMapper.selectEmsPointConfigList(query); + configByKey = new HashMap<>(); + if (configList != null) { + for (EmsPointConfig config : configList) { + if (config != null && StringUtils.isNotBlank(config.getDataKey())) { + configByKey.put(config.getDataKey().toUpperCase(Locale.ROOT), config); + } + } + } + configMapCache.put(cacheKey, configByKey); + } + return configByKey.get(dataKey.toUpperCase(Locale.ROOT)); + } + + private BigDecimal convertPointValue(BigDecimal sourceValue, EmsPointConfig pointConfig) { + if (sourceValue == null || pointConfig == null) { + return sourceValue; + } + BigDecimal a = pointConfig.getDataA() == null ? BigDecimal.ZERO : pointConfig.getDataA(); + BigDecimal k = pointConfig.getDataK() == null ? BigDecimal.ONE : pointConfig.getDataK(); + BigDecimal b = pointConfig.getDataB() == null ? BigDecimal.ZERO : pointConfig.getDataB(); + return a.multiply(sourceValue).multiply(sourceValue) + .add(k.multiply(sourceValue)) + .add(b); + } + + private List parseCsv(MultipartFile file, String siteId) { + List result = new ArrayList<>(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) { + String headerLine = reader.readLine(); + if (StringUtils.isBlank(headerLine)) { + throw new ServiceException("CSV表头不能为空"); + } + List headerList = parseCsvLine(removeUtf8Bom(headerLine)); + Map headerIndex = buildHeaderIndex(headerList); + assertRequiredHeader(headerIndex, "device_category", "device_category"); + assertRequiredHeader(headerIndex, "device_id", "device_id"); + assertRequiredHeader(headerIndex, "data_key", "data_key"); + assertRequiredHeader(headerIndex, "point_desc", "point_desc"); + + String line; + int lineNo = 1; + while ((line = reader.readLine()) != null) { + lineNo++; + if (StringUtils.isBlank(line)) { + continue; + } + List valueList = parseCsvLine(line); + EmsPointConfig pointConfig = new EmsPointConfig(); + pointConfig.setSiteId(siteId); + pointConfig.setDeviceCategory(getRequiredString(valueList, headerIndex, "device_category", lineNo)); + pointConfig.setDeviceId(getRequiredString(valueList, headerIndex, "device_id", lineNo)); + pointConfig.setDataKey(getRequiredString(valueList, headerIndex, "data_key", lineNo)); + pointConfig.setPointDesc(getRequiredString(valueList, headerIndex, "point_desc", lineNo)); + String registerAddress = getString(valueList, headerIndex, "register_address"); + pointConfig.setRegisterAddress(StringUtils.isBlank(registerAddress) ? "" : registerAddress); + pointConfig.setPointName(getString(valueList, headerIndex, "point_name")); + pointConfig.setDataUnit(getString(valueList, headerIndex, "data_unit")); + pointConfig.setDataA(getDecimal(valueList, headerIndex, "data_a", lineNo)); + pointConfig.setDataK(getDecimal(valueList, headerIndex, "data_k", lineNo)); + pointConfig.setDataB(getDecimal(valueList, headerIndex, "data_b", lineNo)); + pointConfig.setDataBit(getInteger(valueList, headerIndex, "data_bit", lineNo)); + pointConfig.setIsAlarm(getInteger(valueList, headerIndex, "is_alarm", lineNo)); + pointConfig.setPointType(getString(valueList, headerIndex, "point_type")); + pointConfig.setCalcExpression(getString(valueList, headerIndex, "calc_expression")); + pointConfig.setRemark(getString(valueList, headerIndex, "remark")); + normalizeAndValidatePointConfig(pointConfig); + result.add(pointConfig); + } + } catch (IOException e) { + throw new ServiceException("CSV读取失败: " + e.getMessage()); + } + return result; + } + + private void assertRequiredHeader(Map headerIndex, String headerKey, String headerName) { + if (!headerIndex.containsKey(headerKey)) { + throw new ServiceException("CSV缺少必填列: " + headerName); + } + } + + private Map buildHeaderIndex(List headerList) { + Map headerIndex = new HashMap<>(); + for (int i = 0; i < headerList.size(); i++) { + String normalizedHeader = toCanonicalHeaderKey(headerList.get(i)); + if (StringUtils.isBlank(normalizedHeader)) { + continue; + } + headerIndex.putIfAbsent(normalizedHeader, i); + } + return headerIndex; + } + + private String getRequiredString(List valueList, Map headerIndex, String key, int lineNo) { + String value = getString(valueList, headerIndex, key); + if (StringUtils.isBlank(value)) { + throw new ServiceException(String.format("CSV第%d行字段[%s]不能为空", lineNo, key)); + } + return value; + } + + private String getString(List valueList, Map headerIndex, String key) { + Integer index = headerIndex.get(key); + if (index == null || index < 0 || index >= valueList.size()) { + return null; + } + String value = valueList.get(index); + return StringUtils.isBlank(value) ? null : value.trim(); + } + + private Integer getInteger(List valueList, Map headerIndex, String key, int lineNo) { + String value = getString(valueList, headerIndex, key); + if (StringUtils.isBlank(value)) { + return null; + } + if (!INTEGER_PATTERN.matcher(value).matches()) { + throw new ServiceException(String.format("CSV第%d行字段[%s]必须是整数", lineNo, key)); + } + return Integer.parseInt(value); + } + + private BigDecimal getDecimal(List valueList, Map headerIndex, String key, int lineNo) { + String value = getString(valueList, headerIndex, key); + if (StringUtils.isBlank(value)) { + return null; + } + if (!DECIMAL_PATTERN.matcher(value).matches()) { + throw new ServiceException(String.format("CSV第%d行字段[%s]必须是数字", lineNo, key)); + } + return new BigDecimal(value); + } + + private String removeUtf8Bom(String content) { + if (StringUtils.isBlank(content)) { + return content; + } + if (content.charAt(0) == '\ufeff') { + return content.substring(1); + } + return content; + } + + private String normalizeHeader(String header) { + if (header == null) { + return ""; + } + return header.trim().toLowerCase().replace("-", "_").replace(" ", ""); + } + + private String toCanonicalHeaderKey(String header) { + String normalized = normalizeHeader(header); + if (StringUtils.isBlank(normalized)) { + return ""; + } + String compact = normalized.replace("_", ""); + switch (compact) { + case "siteid": + case "站点id": + return "site_id"; + case "devicecategory": + case "设备类型": + return "device_category"; + case "deviceid": + case "设备id": + return "device_id"; + case "pointname": + case "点位名称": + return "point_name"; + case "datakey": + case "数据键": + return "data_key"; + case "pointdesc": + case "点位描述": + return "point_desc"; + case "registeraddress": + case "寄存器地址": + return "register_address"; + case "dataunit": + case "datounit": + case "单位": + return "data_unit"; + case "dataa": + case "datoa": + case "a系数": + return "data_a"; + case "datak": + case "datok": + case "k系数": + return "data_k"; + case "datab": + case "datob": + case "b系数": + return "data_b"; + case "databit": + case "datobit": + case "位偏移": + return "data_bit"; + case "isalarm": + case "报警点位": + return "is_alarm"; + case "pointtype": + case "点位类型": + return "point_type"; + case "calcexpression": + case "计算表达式": + return "calc_expression"; + case "remark": + case "备注": + return "remark"; + default: + return normalized; + } + } + + private List parseCsvLine(String line) { + List result = new ArrayList<>(); + if (line == null) { + return result; + } + StringBuilder current = new StringBuilder(); + boolean inQuotes = false; + for (int i = 0; i < line.length(); i++) { + char c = line.charAt(i); + if (c == '"') { + if (inQuotes && i + 1 < line.length() && line.charAt(i + 1) == '"') { + current.append('"'); + i++; + } else { + inQuotes = !inQuotes; + } + continue; + } + if (c == ',' && !inQuotes) { + result.add(current.toString()); + current.setLength(0); + continue; + } + current.append(c); + } + result.add(current.toString()); + return result; + } + + private void normalizeAndValidatePointConfig(EmsPointConfig pointConfig) { + if (pointConfig == null) { + return; + } + pointConfig.setPointType(normalizePointType(pointConfig.getPointType())); + pointConfig.setCalcExpression(StringUtils.trimToNull(pointConfig.getCalcExpression())); + if ("calc".equals(pointConfig.getPointType())) { + if (StringUtils.isBlank(pointConfig.getCalcExpression())) { + throw new ServiceException("计算点必须填写计算表达式"); + } + if (!CALC_EXPRESSION_PATTERN.matcher(pointConfig.getCalcExpression()).matches()) { + throw new ServiceException("计算表达式仅支持数字、字母、下划线、空格和四则运算符"); + } + } else { + pointConfig.setCalcExpression(null); + } + } + + private String normalizePointType(String pointType) { + String normalized = StringUtils.trimToEmpty(pointType).toLowerCase(Locale.ROOT); + if ("calc".equals(normalized) || "calculate".equals(normalized) || "计算点".equals(normalized)) { + return "calc"; + } + return "data"; + } + +} diff --git a/ems-system/src/main/resources/mapper/ems/EmsPointConfigMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsPointConfigMapper.xml new file mode 100644 index 0000000..6a0add3 --- /dev/null +++ b/ems-system/src/main/resources/mapper/ems/EmsPointConfigMapper.xml @@ -0,0 +1,231 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select id, site_id, device_category, device_id, point_name, data_key, point_desc, register_address, + data_unit, data_a, data_k, data_b, data_bit, is_alarm, point_type, calc_expression, + create_by, create_time, update_by, update_time, remark + from ems_point_config + + + + + + + + insert into ems_point_config + + site_id, + device_category, + device_id, + point_name, + data_key, + point_desc, + register_address, + data_unit, + data_a, + data_k, + data_b, + data_bit, + is_alarm, + point_type, + calc_expression, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{siteId}, + #{deviceCategory}, + #{deviceId}, + #{pointName}, + #{dataKey}, + #{pointDesc}, + #{registerAddress}, + #{dataUnit}, + #{dataA}, + #{dataK}, + #{dataB}, + #{dataBit}, + #{isAlarm}, + #{pointType}, + #{calcExpression}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update ems_point_config + + site_id = #{siteId}, + device_category = #{deviceCategory}, + device_id = #{deviceId}, + point_name = #{pointName}, + data_key = #{dataKey}, + point_desc = #{pointDesc}, + register_address = #{registerAddress}, + data_unit = #{dataUnit}, + data_a = #{dataA}, + data_k = #{dataK}, + data_b = #{dataB}, + data_bit = #{dataBit}, + is_alarm = #{isAlarm}, + point_type = #{pointType}, + calc_expression = #{calcExpression}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where id = #{id} + + + + delete from ems_point_config where id = #{id} + + + + delete from ems_point_config where id in + + #{id} + + + + + + + delete from ems_point_config + where site_id = #{siteId} + + + + insert into ems_point_config ( + site_id, device_category, device_id, point_name, data_key, point_desc, register_address, + data_unit, data_a, data_k, data_b, data_bit, is_alarm, point_type, calc_expression, + create_by, create_time, update_by, update_time, remark + ) + select + #{targetSiteId}, device_category, device_id, point_name, data_key, point_desc, register_address, + data_unit, data_a, data_k, data_b, data_bit, is_alarm, point_type, calc_expression, + #{operName}, now(), #{operName}, now(), remark + from ems_point_config + where site_id = #{templateSiteId} + + + + + + + + + diff --git a/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorDataMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorDataMapper.xml new file mode 100644 index 0000000..ce0109c --- /dev/null +++ b/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorDataMapper.xml @@ -0,0 +1,40 @@ + + + + + + + + insert into ${tableName} ( + site_id, statis_minute, data_json, create_by, create_time, update_by, update_time + ) values ( + #{siteId}, #{statisMinute}, #{dataJson}, #{operName}, now(), #{operName}, now() + ) + on duplicate key update + data_json = values(data_json), + update_by = values(update_by), + update_time = now() + + + + update ${tableName} + set hot_soc = #{hotSoc}, + hot_total_active_power = #{hotTotalActivePower}, + hot_total_reactive_power = #{hotTotalReactivePower}, + hot_day_charged_cap = #{hotDayChargedCap}, + hot_day_dis_charged_cap = #{hotDayDisChargedCap}, + update_by = #{operName}, + update_time = now() + where site_id = #{siteId} + and statis_minute = #{statisMinute} + + + diff --git a/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorItemMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorItemMapper.xml new file mode 100644 index 0000000..14635c6 --- /dev/null +++ b/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorItemMapper.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorPointMatchMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorPointMatchMapper.xml new file mode 100644 index 0000000..6627ba2 --- /dev/null +++ b/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorPointMatchMapper.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + delete from ems_site_monitor_point_match + where site_id = #{siteId} + + + + insert into ems_site_monitor_point_match + (site_id, field_code, data_point, create_by, create_time, update_by, update_time) + values + + (#{item.siteId}, #{item.fieldCode}, #{item.dataPoint}, #{item.createBy}, now(), #{item.updateBy}, now()) + + + +