Compare commits
9 Commits
2026-05
...
f7154db93d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7154db93d | ||
|
|
557022d346 | ||
|
|
e43ab264e0 | ||
| 1c979e248a | |||
| 36962221f5 | |||
|
|
24bdaa1ae9 | ||
|
|
58ca8b0c23 | ||
|
|
8a92ff3be0 | ||
|
|
ddadf26837 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,6 +5,8 @@
|
||||
target/
|
||||
logs/
|
||||
docs/
|
||||
.codex-tmp/
|
||||
.docs/
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
package com.njcn.gather.steady.checksquare.component;
|
||||
|
||||
import com.njcn.gather.steady.checksquare.pojo.bo.SteadyChecksquareValuePointBO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityDetailVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityRuleVO;
|
||||
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendIndicatorDefinitionBO;
|
||||
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 谐波偶次与局部奇次基线关系规则。
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class SteadyChecksquareHarmonicParityRuleComponent {
|
||||
|
||||
private static final DateTimeFormatter OUTPUT_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
private static final BigDecimal THRESHOLD_MULTIPLIER = new BigDecimal("2");
|
||||
private static final BigDecimal EVEN_HARMONIC_DEADBAND_VALUE = new BigDecimal("0.1");
|
||||
private static final int MIN_ODD_REFERENCE_COUNT = 2;
|
||||
|
||||
private final SteadyChecksquareInfluxQueryComponent influxQueryComponent;
|
||||
|
||||
public SteadyChecksquareHarmonicParityRuleVO check(String lineId, SteadyTrendIndicatorDefinitionBO indicator,
|
||||
LocalDateTime startTime, LocalDateTime endTime,
|
||||
int intervalMinutes) {
|
||||
SteadyChecksquareHarmonicParityRuleVO result = new SteadyChecksquareHarmonicParityRuleVO();
|
||||
if (!supportHarmonicParityRule(indicator)) {
|
||||
return result;
|
||||
}
|
||||
for (String statType : indicator.getSupportStats()) {
|
||||
for (String phase : indicator.getPhaseCodes()) {
|
||||
Map<Integer, Map<LocalDateTime, BigDecimal>> valueMap = queryOrderValueMap(lineId, indicator, phase,
|
||||
statType, startTime, endTime, intervalMinutes);
|
||||
appendAbnormalDetails(result, phase, statType, indicator, valueMap);
|
||||
}
|
||||
}
|
||||
result.setAbnormalPointCount(result.getAbnormalDetails().size());
|
||||
result.setAbnormal(result.getAbnormalPointCount() > 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean supportHarmonicParityRule(SteadyTrendIndicatorDefinitionBO indicator) {
|
||||
return indicator != null && Boolean.TRUE.equals(indicator.getHarmonic())
|
||||
&& indicator.getHarmonicOrderStart() != null && indicator.getHarmonicOrderEnd() != null;
|
||||
}
|
||||
|
||||
private Map<Integer, Map<LocalDateTime, BigDecimal>> queryOrderValueMap(String lineId,
|
||||
SteadyTrendIndicatorDefinitionBO indicator,
|
||||
String phase, String statType,
|
||||
LocalDateTime startTime,
|
||||
LocalDateTime endTime,
|
||||
int intervalMinutes) {
|
||||
Map<Integer, Map<LocalDateTime, BigDecimal>> result = new LinkedHashMap<Integer, Map<LocalDateTime, BigDecimal>>();
|
||||
List<SteadyTrendResolvedFieldBO> fields = new ArrayList<SteadyTrendResolvedFieldBO>();
|
||||
for (int order = indicator.getHarmonicOrderStart(); order <= indicator.getHarmonicOrderEnd(); order++) {
|
||||
fields.add(buildResolvedField(lineId, indicator, order, phase, statType));
|
||||
}
|
||||
Map<String, List<SteadyChecksquareValuePointBO>> fieldValueMap =
|
||||
influxQueryComponent.queryValuePointMap(fields, startTime, endTime, intervalMinutes);
|
||||
if (fieldValueMap == null) {
|
||||
fieldValueMap = Collections.emptyMap();
|
||||
}
|
||||
for (int order = indicator.getHarmonicOrderStart(); order <= indicator.getHarmonicOrderEnd(); order++) {
|
||||
result.put(order, toValueMap(fieldValueMap.get(indicator.getHarmonicFieldPrefix() + "_" + order)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void appendAbnormalDetails(SteadyChecksquareHarmonicParityRuleVO result, String phase, String statType,
|
||||
SteadyTrendIndicatorDefinitionBO indicator,
|
||||
Map<Integer, Map<LocalDateTime, BigDecimal>> valueMap) {
|
||||
for (int order = firstEvenOrder(indicator.getHarmonicOrderStart()); order <= indicator.getHarmonicOrderEnd(); order += 2) {
|
||||
Map<LocalDateTime, BigDecimal> evenValues = valueMap.get(order);
|
||||
if (evenValues == null || evenValues.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
for (Map.Entry<LocalDateTime, BigDecimal> entry : evenValues.entrySet()) {
|
||||
appendAbnormalDetailIfNecessary(result, phase, statType, order, entry.getKey(), entry.getValue(), valueMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void appendAbnormalDetailIfNecessary(SteadyChecksquareHarmonicParityRuleVO result, String phase,
|
||||
String statType, int evenOrder, LocalDateTime time,
|
||||
BigDecimal evenValue,
|
||||
Map<Integer, Map<LocalDateTime, BigDecimal>> valueMap) {
|
||||
if (evenValue == null || evenValue.compareTo(EVEN_HARMONIC_DEADBAND_VALUE) <= 0) {
|
||||
return;
|
||||
}
|
||||
List<Integer> oddOrders = buildOddReferenceOrders(evenOrder);
|
||||
List<BigDecimal> oddValues = new ArrayList<BigDecimal>();
|
||||
List<Integer> effectiveOddOrders = new ArrayList<Integer>();
|
||||
for (Integer oddOrder : oddOrders) {
|
||||
Map<LocalDateTime, BigDecimal> values = valueMap.get(oddOrder);
|
||||
BigDecimal oddValue = values == null ? null : values.get(time);
|
||||
if (oddValue != null) {
|
||||
effectiveOddOrders.add(oddOrder);
|
||||
oddValues.add(oddValue);
|
||||
}
|
||||
}
|
||||
if (oddValues.size() < MIN_ODD_REFERENCE_COUNT) {
|
||||
return;
|
||||
}
|
||||
BigDecimal median = calculateMedian(oddValues);
|
||||
if (median == null || evenValue.compareTo(median.multiply(THRESHOLD_MULTIPLIER)) <= 0) {
|
||||
return;
|
||||
}
|
||||
result.getAbnormalDetails().add(buildDetail(time, phase, statType, evenOrder, evenValue,
|
||||
effectiveOddOrders, oddValues, median));
|
||||
}
|
||||
|
||||
private SteadyChecksquareHarmonicParityDetailVO buildDetail(LocalDateTime time, String phase, String statType,
|
||||
Integer evenOrder, BigDecimal evenValue,
|
||||
List<Integer> oddOrders, List<BigDecimal> oddValues,
|
||||
BigDecimal median) {
|
||||
SteadyChecksquareHarmonicParityDetailVO detail = new SteadyChecksquareHarmonicParityDetailVO();
|
||||
detail.setTime(OUTPUT_TIME_FORMATTER.format(time));
|
||||
detail.setPhase(phase);
|
||||
detail.setStatType(statType);
|
||||
detail.setEvenHarmonicOrder(evenOrder);
|
||||
detail.setEvenValue(evenValue);
|
||||
detail.setOddHarmonicOrders(new ArrayList<Integer>(oddOrders));
|
||||
detail.setOddValues(new ArrayList<BigDecimal>(oddValues));
|
||||
detail.setOddMedianValue(median);
|
||||
detail.setThresholdMultiplier(THRESHOLD_MULTIPLIER);
|
||||
return detail;
|
||||
}
|
||||
|
||||
private List<Integer> buildOddReferenceOrders(int evenOrder) {
|
||||
List<Integer> result = new ArrayList<Integer>();
|
||||
result.add(evenOrder - 3);
|
||||
result.add(evenOrder - 1);
|
||||
result.add(evenOrder + 1);
|
||||
result.add(evenOrder + 3);
|
||||
return result;
|
||||
}
|
||||
|
||||
private BigDecimal calculateMedian(List<BigDecimal> values) {
|
||||
if (values == null || values.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
List<BigDecimal> sorted = new ArrayList<BigDecimal>(values);
|
||||
Collections.sort(sorted, Comparator.naturalOrder());
|
||||
int middleIndex = sorted.size() / 2;
|
||||
if (sorted.size() % 2 == 1) {
|
||||
return sorted.get(middleIndex);
|
||||
}
|
||||
return sorted.get(middleIndex - 1).add(sorted.get(middleIndex)).divide(new BigDecimal("2"));
|
||||
}
|
||||
|
||||
private int firstEvenOrder(int startOrder) {
|
||||
return startOrder % 2 == 0 ? startOrder : startOrder + 1;
|
||||
}
|
||||
|
||||
private Map<LocalDateTime, BigDecimal> toValueMap(List<SteadyChecksquareValuePointBO> points) {
|
||||
Map<LocalDateTime, BigDecimal> result = new LinkedHashMap<LocalDateTime, BigDecimal>();
|
||||
if (points == null || points.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
for (SteadyChecksquareValuePointBO point : points) {
|
||||
if (point != null && point.getTime() != null && point.getValue() != null) {
|
||||
result.put(point.getTime(), point.getValue());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private SteadyTrendResolvedFieldBO buildResolvedField(String lineId, SteadyTrendIndicatorDefinitionBO indicator,
|
||||
Integer harmonicOrder, String phase, String statType) {
|
||||
SteadyTrendResolvedFieldBO field = new SteadyTrendResolvedFieldBO();
|
||||
field.setMeasurement(indicator.getTableName());
|
||||
field.setField(indicator.getHarmonicFieldPrefix() + "_" + harmonicOrder);
|
||||
field.setLineId(lineId);
|
||||
field.setIndicatorCode(indicator.getIndicatorCode());
|
||||
field.setIndicatorName(indicator.getName());
|
||||
field.setPhase(phase);
|
||||
field.setStatType(statType);
|
||||
field.setUnit(indicator.getUnit());
|
||||
return field;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.gather.steady.checksquare.pojo.bo.SteadyChecksquareValuePointBO;
|
||||
import com.njcn.gather.steady.datavie.config.SteadyInfluxDbProperties;
|
||||
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -14,6 +15,7 @@ import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.URL;
|
||||
@@ -22,7 +24,11 @@ import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -35,28 +41,121 @@ public class SteadyChecksquareInfluxQueryComponent {
|
||||
|
||||
private static final DateTimeFormatter INFLUX_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
private static final ThreadLocal<Map<String, List<SteadyChecksquareValuePointBO>>> REQUEST_VALUE_CACHE =
|
||||
new ThreadLocal<Map<String, List<SteadyChecksquareValuePointBO>>>();
|
||||
|
||||
private final SteadyInfluxDbProperties properties;
|
||||
|
||||
public void enableRequestCache() {
|
||||
REQUEST_VALUE_CACHE.set(new LinkedHashMap<String, List<SteadyChecksquareValuePointBO>>());
|
||||
}
|
||||
|
||||
public void clearRequestCache() {
|
||||
REQUEST_VALUE_CACHE.remove();
|
||||
}
|
||||
|
||||
public Set<LocalDateTime> queryExistingSlots(SteadyTrendResolvedFieldBO field, LocalDateTime startTime,
|
||||
LocalDateTime endTime, int intervalMinutes) {
|
||||
List<SteadyChecksquareValuePointBO> points = queryValuePoints(field, startTime, endTime, intervalMinutes);
|
||||
Set<LocalDateTime> result = new HashSet<LocalDateTime>();
|
||||
for (SteadyChecksquareValuePointBO point : points) {
|
||||
if (point != null && point.getTime() != null) {
|
||||
result.add(point.getTime());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<SteadyChecksquareValuePointBO> queryValuePoints(SteadyTrendResolvedFieldBO field, LocalDateTime startTime,
|
||||
LocalDateTime endTime, int intervalMinutes) {
|
||||
validateConfig();
|
||||
String query = buildChecksquareQuery(field, startTime, endTime);
|
||||
String query = buildValuePointQuery(field, startTime, endTime);
|
||||
String cacheKey = buildCacheKey(query, intervalMinutes);
|
||||
Map<String, List<SteadyChecksquareValuePointBO>> cache = REQUEST_VALUE_CACHE.get();
|
||||
if (cache != null && cache.containsKey(cacheKey)) {
|
||||
return new ArrayList<SteadyChecksquareValuePointBO>(cache.get(cacheKey));
|
||||
}
|
||||
long startMillis = System.currentTimeMillis();
|
||||
log.info("数据校验 InfluxDB 查询开始,measurement={},field={},lineId={},phase={},statType={},query={}",
|
||||
log.info("数据校验指标值 InfluxDB 查询开始,measurement={},field={},lineId={},phase={},statType={},query={}",
|
||||
field.getMeasurement(), field.getField(), field.getLineId(), field.getPhase(), field.getStatType(), query);
|
||||
try {
|
||||
String body = executeQuery(query);
|
||||
Set<LocalDateTime> slots = parseExistingSlots(body, intervalMinutes);
|
||||
log.info("数据校验 InfluxDB 查询结束,slotCount={},costMs={}", slots.size(), System.currentTimeMillis() - startMillis);
|
||||
return slots;
|
||||
List<SteadyChecksquareValuePointBO> points = parseValuePoints(body, intervalMinutes);
|
||||
if (cache != null) {
|
||||
cache.put(cacheKey, new ArrayList<SteadyChecksquareValuePointBO>(points));
|
||||
}
|
||||
log.info("数据校验指标值 InfluxDB 查询结束,pointCount={},costMs={}", points.size(), System.currentTimeMillis() - startMillis);
|
||||
return points;
|
||||
} catch (RuntimeException ex) {
|
||||
log.warn("数据校验 InfluxDB 查询异常,costMs={},error={}", System.currentTimeMillis() - startMillis, ex.getMessage());
|
||||
log.warn("数据校验指标值 InfluxDB 查询异常,costMs={},error={}", System.currentTimeMillis() - startMillis, ex.getMessage());
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, List<SteadyChecksquareValuePointBO>> queryValuePointMap(List<SteadyTrendResolvedFieldBO> fields,
|
||||
LocalDateTime startTime,
|
||||
LocalDateTime endTime,
|
||||
int intervalMinutes) {
|
||||
Map<String, List<SteadyChecksquareValuePointBO>> result =
|
||||
new LinkedHashMap<String, List<SteadyChecksquareValuePointBO>>();
|
||||
if (fields == null || fields.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
if (fields.size() == 1) {
|
||||
SteadyTrendResolvedFieldBO field = fields.get(0);
|
||||
result.put(field.getField(), queryValuePoints(field, startTime, endTime, intervalMinutes));
|
||||
return result;
|
||||
}
|
||||
validateConfig();
|
||||
Map<String, List<SteadyChecksquareValuePointBO>> cache = REQUEST_VALUE_CACHE.get();
|
||||
List<SteadyTrendResolvedFieldBO> missingFields = new ArrayList<SteadyTrendResolvedFieldBO>();
|
||||
for (SteadyTrendResolvedFieldBO field : fields) {
|
||||
String cacheKey = buildCacheKey(buildValuePointQuery(field, startTime, endTime), intervalMinutes);
|
||||
if (cache != null && cache.containsKey(cacheKey)) {
|
||||
result.put(field.getField(), new ArrayList<SteadyChecksquareValuePointBO>(cache.get(cacheKey)));
|
||||
} else {
|
||||
missingFields.add(field);
|
||||
}
|
||||
}
|
||||
if (!missingFields.isEmpty()) {
|
||||
String query = buildBatchValuePointQuery(missingFields, startTime, endTime);
|
||||
long startMillis = System.currentTimeMillis();
|
||||
SteadyTrendResolvedFieldBO first = missingFields.get(0);
|
||||
log.info("数据校验指标值 InfluxDB 批量查询开始,measurement={},fieldCount={},lineId={},phase={},statType={},query={}",
|
||||
first.getMeasurement(), missingFields.size(), first.getLineId(), first.getPhase(), first.getStatType(), query);
|
||||
try {
|
||||
Map<String, List<SteadyChecksquareValuePointBO>> queried = parseBatchValuePoints(executeQuery(query), intervalMinutes);
|
||||
for (SteadyTrendResolvedFieldBO field : missingFields) {
|
||||
List<SteadyChecksquareValuePointBO> points = queried.get(field.getField());
|
||||
if (points == null) {
|
||||
points = new ArrayList<SteadyChecksquareValuePointBO>();
|
||||
}
|
||||
result.put(field.getField(), points);
|
||||
if (cache != null) {
|
||||
String cacheKey = buildCacheKey(buildValuePointQuery(field, startTime, endTime), intervalMinutes);
|
||||
cache.put(cacheKey, new ArrayList<SteadyChecksquareValuePointBO>(points));
|
||||
}
|
||||
}
|
||||
log.info("数据校验指标值 InfluxDB 批量查询结束,fieldCount={},costMs={}",
|
||||
missingFields.size(), System.currentTimeMillis() - startMillis);
|
||||
} catch (RuntimeException ex) {
|
||||
log.warn("数据校验指标值 InfluxDB 批量查询异常,fieldCount={},costMs={},error={}",
|
||||
missingFields.size(), System.currentTimeMillis() - startMillis, ex.getMessage());
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public String buildChecksquareQuery(SteadyTrendResolvedFieldBO field, LocalDateTime startTime, LocalDateTime endTime) {
|
||||
return buildValuePointQuery(field, startTime, endTime);
|
||||
}
|
||||
|
||||
private String buildCacheKey(String query, int intervalMinutes) {
|
||||
return query + "|intervalMinutes=" + intervalMinutes;
|
||||
}
|
||||
|
||||
public String buildValuePointQuery(SteadyTrendResolvedFieldBO field, LocalDateTime startTime, LocalDateTime endTime) {
|
||||
StringBuilder sql = new StringBuilder();
|
||||
sql.append("SELECT \"").append(field.getField()).append("\" AS \"value\"");
|
||||
sql.append(" FROM \"").append(field.getMeasurement()).append("\"");
|
||||
@@ -71,11 +170,33 @@ public class SteadyChecksquareInfluxQueryComponent {
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
private Set<LocalDateTime> parseExistingSlots(String body, int intervalMinutes) {
|
||||
public String buildBatchValuePointQuery(List<SteadyTrendResolvedFieldBO> fields, LocalDateTime startTime, LocalDateTime endTime) {
|
||||
SteadyTrendResolvedFieldBO first = fields.get(0);
|
||||
StringBuilder sql = new StringBuilder("SELECT ");
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
SteadyTrendResolvedFieldBO field = fields.get(i);
|
||||
if (i > 0) {
|
||||
sql.append(", ");
|
||||
}
|
||||
sql.append("\"").append(field.getField()).append("\" AS \"").append(field.getField()).append("\"");
|
||||
}
|
||||
sql.append(" FROM \"").append(first.getMeasurement()).append("\"");
|
||||
sql.append(" WHERE time >= '").append(INFLUX_TIME_FORMATTER.format(startTime)).append("'");
|
||||
sql.append(" AND time <= '").append(INFLUX_TIME_FORMATTER.format(endTime)).append("'");
|
||||
sql.append(" AND \"line_id\" = '").append(escapeTagValue(first.getLineId())).append("'");
|
||||
sql.append(" AND \"phasic_type\" = '").append(escapeTagValue(first.getPhase())).append("'");
|
||||
if (hasValueTypeTag(first.getMeasurement())) {
|
||||
sql.append(" AND \"value_type\" = '").append(resolveValueType(first.getStatType())).append("'");
|
||||
}
|
||||
sql.append(" ORDER BY time ASC");
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
private List<SteadyChecksquareValuePointBO> parseValuePoints(String body, int intervalMinutes) {
|
||||
try {
|
||||
JsonNode root = OBJECT_MAPPER.readTree(body);
|
||||
JsonNode values = root.path("results").path(0).path("series").path(0).path("values");
|
||||
Set<LocalDateTime> result = new HashSet<LocalDateTime>();
|
||||
List<SteadyChecksquareValuePointBO> result = new ArrayList<SteadyChecksquareValuePointBO>();
|
||||
if (!values.isArray()) {
|
||||
return result;
|
||||
}
|
||||
@@ -84,13 +205,64 @@ public class SteadyChecksquareInfluxQueryComponent {
|
||||
continue;
|
||||
}
|
||||
LocalDateTime time = parseInfluxTime(value.get(0).asText());
|
||||
if (time != null) {
|
||||
result.add(alignToPreviousSlot(time, intervalMinutes));
|
||||
if (time == null) {
|
||||
continue;
|
||||
}
|
||||
SteadyChecksquareValuePointBO point = new SteadyChecksquareValuePointBO();
|
||||
point.setTime(alignToPreviousSlot(time, intervalMinutes));
|
||||
point.setValue(new BigDecimal(value.get(1).asText()));
|
||||
result.add(point);
|
||||
}
|
||||
return result;
|
||||
} catch (IOException ex) {
|
||||
throw fail("InfluxDB 返回结果解析失败:" + ex.getMessage());
|
||||
} catch (NumberFormatException ex) {
|
||||
throw fail("InfluxDB 返回指标值格式不正确:" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, List<SteadyChecksquareValuePointBO>> parseBatchValuePoints(String body, int intervalMinutes) {
|
||||
try {
|
||||
JsonNode root = OBJECT_MAPPER.readTree(body);
|
||||
JsonNode series = root.path("results").path(0).path("series").path(0);
|
||||
JsonNode columns = series.path("columns");
|
||||
JsonNode values = series.path("values");
|
||||
Map<Integer, String> columnMap = new LinkedHashMap<Integer, String>();
|
||||
Map<String, List<SteadyChecksquareValuePointBO>> result =
|
||||
new LinkedHashMap<String, List<SteadyChecksquareValuePointBO>>();
|
||||
if (!columns.isArray() || !values.isArray()) {
|
||||
return result;
|
||||
}
|
||||
for (int i = 1; i < columns.size(); i++) {
|
||||
String fieldName = columns.get(i).asText();
|
||||
columnMap.put(i, fieldName);
|
||||
result.put(fieldName, new ArrayList<SteadyChecksquareValuePointBO>());
|
||||
}
|
||||
for (JsonNode row : values) {
|
||||
if (row.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
LocalDateTime time = parseInfluxTime(row.get(0).asText());
|
||||
if (time == null) {
|
||||
continue;
|
||||
}
|
||||
LocalDateTime slot = alignToPreviousSlot(time, intervalMinutes);
|
||||
for (Map.Entry<Integer, String> entry : columnMap.entrySet()) {
|
||||
JsonNode value = row.get(entry.getKey());
|
||||
if (value == null || value.isNull()) {
|
||||
continue;
|
||||
}
|
||||
SteadyChecksquareValuePointBO point = new SteadyChecksquareValuePointBO();
|
||||
point.setTime(slot);
|
||||
point.setValue(new BigDecimal(value.asText()));
|
||||
result.get(entry.getValue()).add(point);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (IOException ex) {
|
||||
throw fail("InfluxDB 返回结果解析失败:" + ex.getMessage());
|
||||
} catch (NumberFormatException ex) {
|
||||
throw fail("InfluxDB 返回指标值格式不正确:" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
package com.njcn.gather.steady.checksquare.component;
|
||||
|
||||
import com.njcn.gather.steady.checksquare.pojo.bo.SteadyChecksquareValuePointBO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareValueOrderDetailVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareValueOrderRuleVO;
|
||||
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendIndicatorDefinitionBO;
|
||||
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO;
|
||||
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendSeriesFieldBO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 数据校验指标值大小关系规则。
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class SteadyChecksquareValueOrderRuleComponent {
|
||||
|
||||
private static final DateTimeFormatter OUTPUT_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
private static final List<String> REQUIRED_STATS = Collections.unmodifiableList(Arrays.asList("MAX", "CP95", "AVG", "MIN"));
|
||||
private static final int ABNORMAL_THRESHOLD = 1;
|
||||
|
||||
private final SteadyChecksquareInfluxQueryComponent influxQueryComponent;
|
||||
|
||||
public SteadyChecksquareValueOrderRuleVO check(String lineId, SteadyTrendIndicatorDefinitionBO indicator,
|
||||
Integer harmonicOrder, LocalDateTime startTime,
|
||||
LocalDateTime endTime, int intervalMinutes) {
|
||||
SteadyChecksquareValueOrderRuleVO result = new SteadyChecksquareValueOrderRuleVO();
|
||||
if (!supportValueOrderRule(indicator)) {
|
||||
return result;
|
||||
}
|
||||
for (String phase : indicator.getPhaseCodes()) {
|
||||
Map<String, Map<LocalDateTime, BigDecimal>> statValueMap = queryStatValueMap(lineId, indicator,
|
||||
harmonicOrder, phase, startTime, endTime, intervalMinutes);
|
||||
appendAbnormalDetails(result, phase, harmonicOrder, statValueMap);
|
||||
}
|
||||
result.setAbnormalPointCount(result.getAbnormalDetails().size());
|
||||
result.setAbnormal(result.getAbnormalPointCount() > ABNORMAL_THRESHOLD);
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean supportValueOrderRule(SteadyTrendIndicatorDefinitionBO indicator) {
|
||||
return indicator != null && indicator.getSupportStats() != null && indicator.getSupportStats().containsAll(REQUIRED_STATS);
|
||||
}
|
||||
|
||||
private Map<String, Map<LocalDateTime, BigDecimal>> queryStatValueMap(String lineId,
|
||||
SteadyTrendIndicatorDefinitionBO indicator,
|
||||
Integer harmonicOrder, String phase,
|
||||
LocalDateTime startTime, LocalDateTime endTime,
|
||||
int intervalMinutes) {
|
||||
Map<String, Map<LocalDateTime, BigDecimal>> result = new LinkedHashMap<String, Map<LocalDateTime, BigDecimal>>();
|
||||
for (String statType : REQUIRED_STATS) {
|
||||
SteadyTrendResolvedFieldBO field = buildResolvedField(lineId, indicator, harmonicOrder, phase, statType);
|
||||
result.put(statType, toValueMap(influxQueryComponent.queryValuePoints(field, startTime, endTime, intervalMinutes)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void appendAbnormalDetails(SteadyChecksquareValueOrderRuleVO result, String phase, Integer harmonicOrder,
|
||||
Map<String, Map<LocalDateTime, BigDecimal>> statValueMap) {
|
||||
Map<LocalDateTime, BigDecimal> maxValues = statValueMap.get("MAX");
|
||||
Map<LocalDateTime, BigDecimal> cp95Values = statValueMap.get("CP95");
|
||||
Map<LocalDateTime, BigDecimal> avgValues = statValueMap.get("AVG");
|
||||
Map<LocalDateTime, BigDecimal> minValues = statValueMap.get("MIN");
|
||||
if (maxValues == null || cp95Values == null || avgValues == null || minValues == null) {
|
||||
return;
|
||||
}
|
||||
for (Map.Entry<LocalDateTime, BigDecimal> entry : maxValues.entrySet()) {
|
||||
LocalDateTime time = entry.getKey();
|
||||
BigDecimal maxValue = entry.getValue();
|
||||
BigDecimal cp95Value = cp95Values.get(time);
|
||||
BigDecimal avgValue = avgValues.get(time);
|
||||
BigDecimal minValue = minValues.get(time);
|
||||
// 缺少任一统计值时由缺数校验负责,不重复计入大小关系异常。
|
||||
if (maxValue == null || cp95Value == null || avgValue == null || minValue == null) {
|
||||
continue;
|
||||
}
|
||||
if (maxValue.compareTo(cp95Value) >= 0 && cp95Value.compareTo(avgValue) >= 0 && avgValue.compareTo(minValue) >= 0) {
|
||||
continue;
|
||||
}
|
||||
result.getAbnormalDetails().add(buildDetail(time, phase, harmonicOrder, maxValue, minValue, avgValue, cp95Value));
|
||||
}
|
||||
}
|
||||
|
||||
private SteadyChecksquareValueOrderDetailVO buildDetail(LocalDateTime time, String phase, Integer harmonicOrder,
|
||||
BigDecimal maxValue, BigDecimal minValue,
|
||||
BigDecimal avgValue, BigDecimal cp95Value) {
|
||||
SteadyChecksquareValueOrderDetailVO detail = new SteadyChecksquareValueOrderDetailVO();
|
||||
detail.setTime(OUTPUT_TIME_FORMATTER.format(time));
|
||||
detail.setPhase(phase);
|
||||
detail.setHarmonicOrder(harmonicOrder);
|
||||
detail.setMaxValue(maxValue);
|
||||
detail.setMinValue(minValue);
|
||||
detail.setAvgValue(avgValue);
|
||||
detail.setCp95Value(cp95Value);
|
||||
return detail;
|
||||
}
|
||||
|
||||
private Map<LocalDateTime, BigDecimal> toValueMap(List<SteadyChecksquareValuePointBO> points) {
|
||||
Map<LocalDateTime, BigDecimal> result = new LinkedHashMap<LocalDateTime, BigDecimal>();
|
||||
if (points == null || points.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
for (SteadyChecksquareValuePointBO point : points) {
|
||||
if (point != null && point.getTime() != null && point.getValue() != null) {
|
||||
result.put(point.getTime(), point.getValue());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private SteadyTrendResolvedFieldBO buildResolvedField(String lineId, SteadyTrendIndicatorDefinitionBO indicator,
|
||||
Integer harmonicOrder, String phase, String statType) {
|
||||
SteadyTrendResolvedFieldBO field = new SteadyTrendResolvedFieldBO();
|
||||
field.setMeasurement(indicator.getTableName());
|
||||
field.setField(resolveField(indicator, harmonicOrder));
|
||||
field.setLineId(lineId);
|
||||
field.setIndicatorCode(indicator.getIndicatorCode());
|
||||
field.setIndicatorName(indicator.getName());
|
||||
field.setPhase(phase);
|
||||
field.setStatType(statType);
|
||||
field.setUnit(indicator.getUnit());
|
||||
return field;
|
||||
}
|
||||
|
||||
private String resolveField(SteadyTrendIndicatorDefinitionBO indicator, Integer harmonicOrder) {
|
||||
if (Boolean.TRUE.equals(indicator.getHarmonic())) {
|
||||
return indicator.getHarmonicFieldPrefix() + "_" + harmonicOrder;
|
||||
}
|
||||
List<SteadyTrendSeriesFieldBO> fields = indicator.getSeriesFields();
|
||||
if (fields == null || fields.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return fields.get(0).getField();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,18 @@
|
||||
package com.njcn.gather.steady.checksquare.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||
import com.njcn.common.pojo.constant.OperateType;
|
||||
import com.njcn.common.pojo.enums.common.LogEnum;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.response.HttpResult;
|
||||
import com.njcn.common.utils.LogUtil;
|
||||
import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareHistoryQueryParam;
|
||||
import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareCreateVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemDetailVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareQueryVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareTaskVO;
|
||||
import com.njcn.gather.steady.checksquare.service.SteadyChecksquareService;
|
||||
import com.njcn.web.controller.BaseController;
|
||||
import com.njcn.web.utils.HttpResultUtil;
|
||||
@@ -14,9 +20,12 @@ import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -32,12 +41,43 @@ public class SteadyChecksquareController extends BaseController {
|
||||
private final SteadyChecksquareService checksquareService;
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@ApiOperation("查询数据校验结果")
|
||||
@ApiOperation("查询数据校验历史记录")
|
||||
@PostMapping("/query")
|
||||
public HttpResult<SteadyChecksquareQueryVO> query(@RequestBody SteadyChecksquareQueryParam param) {
|
||||
public HttpResult<Page<SteadyChecksquareTaskVO>> query(@RequestBody @Validated SteadyChecksquareHistoryQueryParam param) {
|
||||
String methodDescribe = getMethodDescribe("query");
|
||||
LogUtil.njcnDebug(log, "{},开始查询数据校验结果,param={}", methodDescribe, param);
|
||||
SteadyChecksquareQueryVO result = checksquareService.query(param);
|
||||
LogUtil.njcnDebug(log, "{},开始查询数据校验历史记录,param={}", methodDescribe, param);
|
||||
Page<SteadyChecksquareTaskVO> result = checksquareService.query(param);
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
|
||||
@ApiOperation("新增数据校验记录")
|
||||
@PostMapping("/create")
|
||||
public HttpResult<SteadyChecksquareCreateVO> create(@RequestBody @Validated SteadyChecksquareQueryParam param) {
|
||||
String methodDescribe = getMethodDescribe("create");
|
||||
LogUtil.njcnDebug(log, "{},开始新增数据校验记录,param={}", methodDescribe, param);
|
||||
SteadyChecksquareCreateVO result = checksquareService.create(param);
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@ApiOperation("查询数据校验任务详情")
|
||||
@GetMapping("/detail")
|
||||
public HttpResult<SteadyChecksquareQueryVO> detail(@RequestParam("taskId") String taskId) {
|
||||
String methodDescribe = getMethodDescribe("detail");
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, checksquareService.detail(taskId), methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@ApiOperation("查询数据校验检测项明细")
|
||||
@GetMapping("/item-detail")
|
||||
public HttpResult<SteadyChecksquareItemDetailVO> itemDetail(@RequestParam("itemId") String itemId,
|
||||
@RequestParam("detailType") String detailType,
|
||||
@RequestParam(value = "statType", required = false) String statType,
|
||||
@RequestParam(value = "pageNum", required = false) Integer pageNum,
|
||||
@RequestParam(value = "pageSize", required = false) Integer pageSize) {
|
||||
String methodDescribe = getMethodDescribe("itemDetail");
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,
|
||||
checksquareService.itemDetail(itemId, detailType, statType, pageNum, pageSize), methodDescribe);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.steady.checksquare.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareDetailPO;
|
||||
|
||||
/**
|
||||
* 数据校验明细 Mapper。
|
||||
*/
|
||||
public interface SteadyChecksquareDetailMapper extends BaseMapper<SteadyChecksquareDetailPO> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.steady.checksquare.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareItemPO;
|
||||
|
||||
/**
|
||||
* 数据校验检测项 Mapper。
|
||||
*/
|
||||
public interface SteadyChecksquareItemMapper extends BaseMapper<SteadyChecksquareItemPO> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.steady.checksquare.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareStatSummaryPO;
|
||||
|
||||
/**
|
||||
* 数据校验统计摘要 Mapper。
|
||||
*/
|
||||
public interface SteadyChecksquareStatSummaryMapper extends BaseMapper<SteadyChecksquareStatSummaryPO> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.steady.checksquare.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareTaskPO;
|
||||
|
||||
/**
|
||||
* 数据校验任务 Mapper。
|
||||
*/
|
||||
public interface SteadyChecksquareTaskMapper extends BaseMapper<SteadyChecksquareTaskPO> {
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.njcn.gather.steady.checksquare.pojo.bo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 数据校验指标值时间点。
|
||||
*/
|
||||
@Data
|
||||
public class SteadyChecksquareValuePointBO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 对齐后的统计时间。 */
|
||||
private LocalDateTime time;
|
||||
|
||||
/** 指标值。 */
|
||||
private BigDecimal value;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.njcn.gather.steady.checksquare.pojo.constant;
|
||||
|
||||
/**
|
||||
* 数据校验常量。
|
||||
*/
|
||||
public final class SteadyChecksquareConst {
|
||||
|
||||
public static final int STATE_DELETED = 0;
|
||||
public static final int STATE_ENABLED = 1;
|
||||
|
||||
public static final String TASK_STATUS_SUCCESS = "SUCCESS";
|
||||
public static final String DETAIL_TYPE_SEGMENT = "SEGMENT";
|
||||
public static final String DETAIL_TYPE_VALUE_ORDER = "VALUE_ORDER";
|
||||
public static final String DETAIL_TYPE_HARMONIC_PARITY = "HARMONIC_PARITY";
|
||||
|
||||
private SteadyChecksquareConst() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.njcn.gather.steady.checksquare.pojo.param;
|
||||
|
||||
import com.njcn.web.pojo.param.BaseParam;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 数据校验历史查询参数。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ApiModel("数据校验历史查询参数")
|
||||
public class SteadyChecksquareHistoryQueryParam extends BaseParam {
|
||||
|
||||
@ApiModelProperty("监测点 ID")
|
||||
private String lineId;
|
||||
|
||||
@ApiModelProperty("指标编码")
|
||||
private String indicatorCode;
|
||||
|
||||
@ApiModelProperty("检测开始时间,格式 yyyy-MM-dd HH:mm:ss")
|
||||
private String timeStart;
|
||||
|
||||
@ApiModelProperty("检测结束时间,格式 yyyy-MM-dd HH:mm:ss")
|
||||
private String timeEnd;
|
||||
|
||||
@ApiModelProperty("是否存在异常")
|
||||
private Boolean hasAbnormal;
|
||||
}
|
||||
@@ -8,10 +8,10 @@ import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据校验查询参数。
|
||||
* 数据校验新增检测参数。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("数据校验查询参数")
|
||||
@ApiModel("数据校验新增检测参数")
|
||||
public class SteadyChecksquareQueryParam implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -27,7 +27,4 @@ public class SteadyChecksquareQueryParam implements Serializable {
|
||||
|
||||
@ApiModelProperty("结束时间,格式 yyyy-MM-dd HH:mm:ss")
|
||||
private String timeEnd;
|
||||
|
||||
@ApiModelProperty("谐波次数,谐波指标按请求次数查询")
|
||||
private List<Integer> harmonicOrders;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.njcn.gather.steady.checksquare.pojo.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 数据校验明细。
|
||||
*/
|
||||
@Data
|
||||
@TableName("steady_checksquare_detail")
|
||||
public class SteadyChecksquareDetailPO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId("id")
|
||||
private String id;
|
||||
@TableField("item_id")
|
||||
private String itemId;
|
||||
@TableField("detail_type")
|
||||
private String detailType;
|
||||
@TableField("stat_type")
|
||||
private String statType;
|
||||
@TableField("start_time")
|
||||
private LocalDateTime startTime;
|
||||
@TableField("end_time")
|
||||
private LocalDateTime endTime;
|
||||
@TableField("point_time")
|
||||
private LocalDateTime pointTime;
|
||||
@TableField("segment_status")
|
||||
private String segmentStatus;
|
||||
@TableField("missing_point_count")
|
||||
private Integer missingPointCount;
|
||||
@TableField("duration_minutes")
|
||||
private Integer durationMinutes;
|
||||
@TableField("phase")
|
||||
private String phase;
|
||||
@TableField("harmonic_order")
|
||||
private Integer harmonicOrder;
|
||||
@TableField("max_value")
|
||||
private BigDecimal maxValue;
|
||||
@TableField("min_value")
|
||||
private BigDecimal minValue;
|
||||
@TableField("avg_value")
|
||||
private BigDecimal avgValue;
|
||||
@TableField("cp95_value")
|
||||
private BigDecimal cp95Value;
|
||||
@TableField("even_harmonic_order")
|
||||
private Integer evenHarmonicOrder;
|
||||
@TableField("even_value")
|
||||
private BigDecimal evenValue;
|
||||
@TableField("odd_harmonic_orders_json")
|
||||
private String oddHarmonicOrdersJson;
|
||||
@TableField("odd_values_json")
|
||||
private String oddValuesJson;
|
||||
@TableField("odd_median_value")
|
||||
private BigDecimal oddMedianValue;
|
||||
@TableField("threshold_multiplier")
|
||||
private BigDecimal thresholdMultiplier;
|
||||
@TableField("create_time")
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.njcn.gather.steady.checksquare.pojo.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 数据校验检测项。
|
||||
*/
|
||||
@Data
|
||||
@TableName("steady_checksquare_item")
|
||||
public class SteadyChecksquareItemPO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId("id")
|
||||
private String id;
|
||||
@TableField("task_id")
|
||||
private String taskId;
|
||||
@TableField("item_key")
|
||||
private String itemKey;
|
||||
@TableField("indicator_code")
|
||||
private String indicatorCode;
|
||||
@TableField("indicator_name")
|
||||
private String indicatorName;
|
||||
@TableField("harmonic_order")
|
||||
private Integer harmonicOrder;
|
||||
@TableField("interval_minutes")
|
||||
private Integer intervalMinutes;
|
||||
@TableField("has_data")
|
||||
private Integer hasData;
|
||||
@TableField("expected_point_count")
|
||||
private Integer expectedPointCount;
|
||||
@TableField("actual_point_count")
|
||||
private Integer actualPointCount;
|
||||
@TableField("missing_point_count")
|
||||
private Integer missingPointCount;
|
||||
@TableField("missing_rate")
|
||||
private BigDecimal missingRate;
|
||||
@TableField("missing_rate_text")
|
||||
private String missingRateText;
|
||||
@TableField("max_continuous_missing_minutes")
|
||||
private Integer maxContinuousMissingMinutes;
|
||||
@TableField("abnormal")
|
||||
private Integer abnormal;
|
||||
@TableField("abnormal_point_count")
|
||||
private Integer abnormalPointCount;
|
||||
@TableField("harmonic_parity_abnormal")
|
||||
private Integer harmonicParityAbnormal;
|
||||
@TableField("harmonic_parity_abnormal_point_count")
|
||||
private Integer harmonicParityAbnormalPointCount;
|
||||
@TableField("state")
|
||||
private Integer state;
|
||||
@TableField("create_time")
|
||||
private LocalDateTime createTime;
|
||||
@TableField("update_time")
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.njcn.gather.steady.checksquare.pojo.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 数据校验统计摘要。
|
||||
*/
|
||||
@Data
|
||||
@TableName("steady_checksquare_stat_summary")
|
||||
public class SteadyChecksquareStatSummaryPO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId("id")
|
||||
private String id;
|
||||
@TableField("item_id")
|
||||
private String itemId;
|
||||
@TableField("stat_type")
|
||||
private String statType;
|
||||
@TableField("supported")
|
||||
private Integer supported;
|
||||
@TableField("has_data")
|
||||
private Integer hasData;
|
||||
@TableField("expected_point_count")
|
||||
private Integer expectedPointCount;
|
||||
@TableField("actual_point_count")
|
||||
private Integer actualPointCount;
|
||||
@TableField("missing_point_count")
|
||||
private Integer missingPointCount;
|
||||
@TableField("missing_rate")
|
||||
private BigDecimal missingRate;
|
||||
@TableField("missing_rate_text")
|
||||
private String missingRateText;
|
||||
@TableField("max_continuous_missing_minutes")
|
||||
private Integer maxContinuousMissingMinutes;
|
||||
@TableField("create_time")
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.njcn.gather.steady.checksquare.pojo.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 数据校验任务。
|
||||
*/
|
||||
@Data
|
||||
@TableName("steady_checksquare_task")
|
||||
public class SteadyChecksquareTaskPO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId("id")
|
||||
private String id;
|
||||
@TableField("task_no")
|
||||
private String taskNo;
|
||||
@TableField("line_id")
|
||||
private String lineId;
|
||||
@TableField("line_name")
|
||||
private String lineName;
|
||||
@TableField("time_start")
|
||||
private LocalDateTime timeStart;
|
||||
@TableField("time_end")
|
||||
private LocalDateTime timeEnd;
|
||||
@TableField("interval_minutes")
|
||||
private Integer intervalMinutes;
|
||||
@TableField("indicator_codes_json")
|
||||
private String indicatorCodesJson;
|
||||
@TableField("indicator_codes_text")
|
||||
private String indicatorCodesText;
|
||||
@TableField("task_status")
|
||||
private String taskStatus;
|
||||
@TableField("item_count")
|
||||
private Integer itemCount;
|
||||
@TableField("abnormal_item_count")
|
||||
private Integer abnormalItemCount;
|
||||
@TableField("max_missing_rate")
|
||||
private BigDecimal maxMissingRate;
|
||||
@TableField("result_message")
|
||||
private String resultMessage;
|
||||
@TableField("state")
|
||||
private Integer state;
|
||||
@TableField("create_by")
|
||||
private String createBy;
|
||||
@TableField("create_time")
|
||||
private LocalDateTime createTime;
|
||||
@TableField("update_by")
|
||||
private String updateBy;
|
||||
@TableField("update_time")
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.njcn.gather.steady.checksquare.pojo.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 新增数据校验结果。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("新增数据校验结果")
|
||||
public class SteadyChecksquareCreateVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("任务 ID")
|
||||
private String taskId;
|
||||
|
||||
@ApiModelProperty("任务编号")
|
||||
private String taskNo;
|
||||
|
||||
@ApiModelProperty("监测点 ID")
|
||||
private String lineId;
|
||||
|
||||
@ApiModelProperty("监测点名称")
|
||||
private String lineName;
|
||||
|
||||
@ApiModelProperty("开始时间")
|
||||
private String timeStart;
|
||||
|
||||
@ApiModelProperty("结束时间")
|
||||
private String timeEnd;
|
||||
|
||||
@ApiModelProperty("统计间隔,单位分钟")
|
||||
private Integer intervalMinutes;
|
||||
|
||||
@ApiModelProperty("检测项数量")
|
||||
private Integer itemCount;
|
||||
|
||||
@ApiModelProperty("异常检测项数量")
|
||||
private Integer abnormalItemCount;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.njcn.gather.steady.checksquare.pojo.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 谐波奇偶关系异常明细。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("谐波奇偶关系异常明细")
|
||||
public class SteadyChecksquareHarmonicParityDetailVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("时间")
|
||||
private String time;
|
||||
|
||||
@ApiModelProperty("相别")
|
||||
private String phase;
|
||||
|
||||
@ApiModelProperty("统计类型")
|
||||
private String statType;
|
||||
|
||||
@ApiModelProperty("偶次谐波次数")
|
||||
private Integer evenHarmonicOrder;
|
||||
|
||||
@ApiModelProperty("偶次谐波值")
|
||||
private BigDecimal evenValue;
|
||||
|
||||
@ApiModelProperty("参与比较的奇次谐波次数")
|
||||
private List<Integer> oddHarmonicOrders = new ArrayList<Integer>();
|
||||
|
||||
@ApiModelProperty("参与比较的奇次谐波值")
|
||||
private List<BigDecimal> oddValues = new ArrayList<BigDecimal>();
|
||||
|
||||
@ApiModelProperty("奇次谐波中位数")
|
||||
private BigDecimal oddMedianValue;
|
||||
|
||||
@ApiModelProperty("异常阈值倍数")
|
||||
private BigDecimal thresholdMultiplier;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.njcn.gather.steady.checksquare.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 谐波奇偶关系规则结果。
|
||||
*/
|
||||
@Data
|
||||
public class SteadyChecksquareHarmonicParityRuleVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Boolean abnormal = false;
|
||||
|
||||
private Integer abnormalPointCount = 0;
|
||||
|
||||
private List<SteadyChecksquareHarmonicParityDetailVO> abnormalDetails =
|
||||
new ArrayList<SteadyChecksquareHarmonicParityDetailVO>();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.njcn.gather.steady.checksquare.pojo.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据校验检测项明细。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("数据校验检测项明细")
|
||||
public class SteadyChecksquareItemDetailVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("检测项 ID")
|
||||
private String itemId;
|
||||
|
||||
@ApiModelProperty("明细类型")
|
||||
private String detailType;
|
||||
|
||||
@ApiModelProperty("统计类型")
|
||||
private String statType;
|
||||
|
||||
@ApiModelProperty("当前页码;未分页查询时为空")
|
||||
private Integer pageNum;
|
||||
|
||||
@ApiModelProperty("每页条数;未分页查询时为空")
|
||||
private Integer pageSize;
|
||||
|
||||
@ApiModelProperty("总记录数;未分页查询时为空")
|
||||
private Long total;
|
||||
|
||||
@ApiModelProperty("缺失区间")
|
||||
private List<SteadyChecksquareSegmentVO> segments = new ArrayList<SteadyChecksquareSegmentVO>();
|
||||
|
||||
@ApiModelProperty("大小关系异常明细")
|
||||
private List<SteadyChecksquareValueOrderDetailVO> valueOrderDetails =
|
||||
new ArrayList<SteadyChecksquareValueOrderDetailVO>();
|
||||
|
||||
@ApiModelProperty("谐波奇偶关系异常明细")
|
||||
private List<SteadyChecksquareHarmonicParityDetailVO> harmonicParityDetails =
|
||||
new ArrayList<SteadyChecksquareHarmonicParityDetailVO>();
|
||||
}
|
||||
@@ -18,6 +18,9 @@ public class SteadyChecksquareItemVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("检测项 ID")
|
||||
private String itemId;
|
||||
|
||||
@ApiModelProperty("校验项唯一键")
|
||||
private String itemKey;
|
||||
|
||||
@@ -54,6 +57,25 @@ public class SteadyChecksquareItemVO implements Serializable {
|
||||
@ApiModelProperty("最大连续缺失时长,单位分钟")
|
||||
private Integer maxContinuousMissingMinutes;
|
||||
|
||||
@ApiModelProperty("指标值大小关系是否异常")
|
||||
private Boolean abnormal;
|
||||
|
||||
@ApiModelProperty("指标值大小关系异常累计值")
|
||||
private Integer abnormalPointCount;
|
||||
|
||||
@ApiModelProperty("指标值大小关系异常明细")
|
||||
private List<SteadyChecksquareValueOrderDetailVO> abnormalDetails = new ArrayList<SteadyChecksquareValueOrderDetailVO>();
|
||||
|
||||
@ApiModelProperty("谐波奇偶关系是否异常")
|
||||
private Boolean harmonicParityAbnormal;
|
||||
|
||||
@ApiModelProperty("谐波奇偶关系异常累计值")
|
||||
private Integer harmonicParityAbnormalPointCount;
|
||||
|
||||
@ApiModelProperty("谐波奇偶关系异常明细")
|
||||
private List<SteadyChecksquareHarmonicParityDetailVO> harmonicParityAbnormalDetails =
|
||||
new ArrayList<SteadyChecksquareHarmonicParityDetailVO>();
|
||||
|
||||
@ApiModelProperty("统计类型摘要")
|
||||
private List<SteadyChecksquareStatSummaryVO> statSummaries = new ArrayList<SteadyChecksquareStatSummaryVO>();
|
||||
|
||||
|
||||
@@ -17,6 +17,12 @@ public class SteadyChecksquareQueryVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("任务 ID")
|
||||
private String taskId;
|
||||
|
||||
@ApiModelProperty("任务编号")
|
||||
private String taskNo;
|
||||
|
||||
@ApiModelProperty("监测点 ID")
|
||||
private String lineId;
|
||||
|
||||
|
||||
@@ -24,6 +24,9 @@ public class SteadyChecksquareSegmentVO implements Serializable {
|
||||
@ApiModelProperty("状态,NORMAL/MISSING")
|
||||
private String status;
|
||||
|
||||
@ApiModelProperty("谐波次数")
|
||||
private Integer harmonicOrder;
|
||||
|
||||
@ApiModelProperty("缺失点数")
|
||||
private Integer missingPointCount;
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.njcn.gather.steady.checksquare.pojo.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 数据校验历史任务。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("数据校验历史任务")
|
||||
public class SteadyChecksquareTaskVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("任务 ID")
|
||||
private String taskId;
|
||||
|
||||
@ApiModelProperty("任务编号")
|
||||
private String taskNo;
|
||||
|
||||
@ApiModelProperty("监测点 ID")
|
||||
private String lineId;
|
||||
|
||||
@ApiModelProperty("监测点名称")
|
||||
private String lineName;
|
||||
|
||||
@ApiModelProperty("开始时间")
|
||||
private String timeStart;
|
||||
|
||||
@ApiModelProperty("结束时间")
|
||||
private String timeEnd;
|
||||
|
||||
@ApiModelProperty("统计间隔,单位分钟")
|
||||
private Integer intervalMinutes;
|
||||
|
||||
@ApiModelProperty("任务状态")
|
||||
private String taskStatus;
|
||||
|
||||
@ApiModelProperty("检测项数量")
|
||||
private Integer itemCount;
|
||||
|
||||
@ApiModelProperty("异常检测项数量")
|
||||
private Integer abnormalItemCount;
|
||||
|
||||
@ApiModelProperty("最大缺失率")
|
||||
private BigDecimal maxMissingRate;
|
||||
|
||||
@ApiModelProperty("创建时间")
|
||||
private String createTime;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.njcn.gather.steady.checksquare.pojo.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 数据校验指标值大小关系异常明细。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("数据校验指标值大小关系异常明细")
|
||||
public class SteadyChecksquareValueOrderDetailVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("时间")
|
||||
private String time;
|
||||
|
||||
@ApiModelProperty("相别")
|
||||
private String phase;
|
||||
|
||||
@ApiModelProperty("谐波次数")
|
||||
private Integer harmonicOrder;
|
||||
|
||||
@ApiModelProperty("最大值")
|
||||
private BigDecimal maxValue;
|
||||
|
||||
@ApiModelProperty("最小值")
|
||||
private BigDecimal minValue;
|
||||
|
||||
@ApiModelProperty("平均值")
|
||||
private BigDecimal avgValue;
|
||||
|
||||
@ApiModelProperty("CP95 值")
|
||||
private BigDecimal cp95Value;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.njcn.gather.steady.checksquare.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据校验指标值大小关系规则结果。
|
||||
*/
|
||||
@Data
|
||||
public class SteadyChecksquareValueOrderRuleVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Boolean abnormal = false;
|
||||
|
||||
private Integer abnormalPointCount = 0;
|
||||
|
||||
private List<SteadyChecksquareValueOrderDetailVO> abnormalDetails = new ArrayList<SteadyChecksquareValueOrderDetailVO>();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.steady.checksquare.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareDetailPO;
|
||||
|
||||
/**
|
||||
* 数据校验明细服务。
|
||||
*/
|
||||
public interface SteadyChecksquareDetailService extends IService<SteadyChecksquareDetailPO> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.steady.checksquare.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareItemPO;
|
||||
|
||||
/**
|
||||
* 数据校验检测项服务。
|
||||
*/
|
||||
public interface SteadyChecksquareItemService extends IService<SteadyChecksquareItemPO> {
|
||||
}
|
||||
@@ -1,12 +1,26 @@
|
||||
package com.njcn.gather.steady.checksquare.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareHistoryQueryParam;
|
||||
import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareCreateVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemDetailVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareQueryVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareTaskVO;
|
||||
|
||||
/**
|
||||
* 数据校验服务。
|
||||
*/
|
||||
public interface SteadyChecksquareService {
|
||||
|
||||
SteadyChecksquareQueryVO query(SteadyChecksquareQueryParam param);
|
||||
Page<SteadyChecksquareTaskVO> query(SteadyChecksquareHistoryQueryParam param);
|
||||
|
||||
SteadyChecksquareCreateVO create(SteadyChecksquareQueryParam param);
|
||||
|
||||
SteadyChecksquareQueryVO detail(String taskId);
|
||||
|
||||
SteadyChecksquareItemDetailVO itemDetail(String itemId, String detailType, String statType);
|
||||
|
||||
SteadyChecksquareItemDetailVO itemDetail(String itemId, String detailType, String statType,
|
||||
Integer pageNum, Integer pageSize);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.steady.checksquare.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareStatSummaryPO;
|
||||
|
||||
/**
|
||||
* 数据校验统计摘要服务。
|
||||
*/
|
||||
public interface SteadyChecksquareStatSummaryService extends IService<SteadyChecksquareStatSummaryPO> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.steady.checksquare.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareTaskPO;
|
||||
|
||||
/**
|
||||
* 数据校验任务服务。
|
||||
*/
|
||||
public interface SteadyChecksquareTaskService extends IService<SteadyChecksquareTaskPO> {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.njcn.gather.steady.checksquare.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.njcn.gather.steady.checksquare.mapper.SteadyChecksquareDetailMapper;
|
||||
import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareDetailPO;
|
||||
import com.njcn.gather.steady.checksquare.service.SteadyChecksquareDetailService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 数据校验明细服务实现。
|
||||
*/
|
||||
@Service
|
||||
public class SteadyChecksquareDetailServiceImpl extends ServiceImpl<SteadyChecksquareDetailMapper, SteadyChecksquareDetailPO>
|
||||
implements SteadyChecksquareDetailService {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.njcn.gather.steady.checksquare.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.njcn.gather.steady.checksquare.mapper.SteadyChecksquareItemMapper;
|
||||
import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareItemPO;
|
||||
import com.njcn.gather.steady.checksquare.service.SteadyChecksquareItemService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 数据校验检测项服务实现。
|
||||
*/
|
||||
@Service
|
||||
public class SteadyChecksquareItemServiceImpl extends ServiceImpl<SteadyChecksquareItemMapper, SteadyChecksquareItemPO>
|
||||
implements SteadyChecksquareItemService {
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
package com.njcn.gather.steady.checksquare.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.njcn.gather.steady.checksquare.mapper.SteadyChecksquareStatSummaryMapper;
|
||||
import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareStatSummaryPO;
|
||||
import com.njcn.gather.steady.checksquare.service.SteadyChecksquareStatSummaryService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 数据校验统计摘要服务实现。
|
||||
*/
|
||||
@Service
|
||||
public class SteadyChecksquareStatSummaryServiceImpl
|
||||
extends ServiceImpl<SteadyChecksquareStatSummaryMapper, SteadyChecksquareStatSummaryPO>
|
||||
implements SteadyChecksquareStatSummaryService {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.njcn.gather.steady.checksquare.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.njcn.gather.steady.checksquare.mapper.SteadyChecksquareTaskMapper;
|
||||
import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareTaskPO;
|
||||
import com.njcn.gather.steady.checksquare.service.SteadyChecksquareTaskService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 数据校验任务服务实现。
|
||||
*/
|
||||
@Service
|
||||
public class SteadyChecksquareTaskServiceImpl extends ServiceImpl<SteadyChecksquareTaskMapper, SteadyChecksquareTaskPO>
|
||||
implements SteadyChecksquareTaskService {
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.njcn.gather.steady.checksquare.util;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 数据校验编号工具。
|
||||
*/
|
||||
public final class SteadyChecksquareIdUtil {
|
||||
|
||||
private static final DateTimeFormatter TASK_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
|
||||
|
||||
private SteadyChecksquareIdUtil() {
|
||||
}
|
||||
|
||||
public static String uuid() {
|
||||
return UUID.randomUUID().toString().replace("-", "");
|
||||
}
|
||||
|
||||
public static String taskNo() {
|
||||
return "CS" + LocalDateTime.now().format(TASK_FORMATTER);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
-- 稳态数据查看建议索引。
|
||||
-- 本脚本不自动执行,请按数据库现状审阅后单独执行。
|
||||
|
||||
CREATE INDEX idx_data_v_time_line_phase
|
||||
ON data_v (TIMEID, LINEID, PHASIC_TYPE);
|
||||
|
||||
CREATE INDEX idx_data_i_time_line_phase
|
||||
ON data_i (TIMEID, LINEID, PHASIC_TYPE);
|
||||
|
||||
CREATE INDEX idx_data_flicker_time_line_phase
|
||||
ON data_flicker (TIMEID, LINEID, PHASIC_TYPE);
|
||||
|
||||
CREATE INDEX idx_data_fluc_time_line_phase
|
||||
ON data_fluc (TIMEID, LINEID, PHASIC_TYPE);
|
||||
|
||||
CREATE INDEX idx_data_harmphasic_i_time_line_phase
|
||||
ON data_harmphasic_i (TIMEID, LINEID, PHASIC_TYPE);
|
||||
|
||||
CREATE INDEX idx_data_harmphasic_v_time_line_phase
|
||||
ON data_harmphasic_v (TIMEID, LINEID, PHASIC_TYPE);
|
||||
|
||||
CREATE INDEX idx_data_harmpower_p_time_line_phase
|
||||
ON data_harmpower_p (TIMEID, LINEID, PHASIC_TYPE);
|
||||
|
||||
CREATE INDEX idx_data_harmpower_q_time_line_phase
|
||||
ON data_harmpower_q (TIMEID, LINEID, PHASIC_TYPE);
|
||||
|
||||
CREATE INDEX idx_data_harmpower_s_time_line_phase
|
||||
ON data_harmpower_s (TIMEID, LINEID, PHASIC_TYPE);
|
||||
|
||||
CREATE INDEX idx_data_harmrate_i_time_line_phase
|
||||
ON data_harmrate_i (TIMEID, LINEID, PHASIC_TYPE);
|
||||
|
||||
CREATE INDEX idx_data_harmrate_v_time_line_phase
|
||||
ON data_harmrate_v (TIMEID, LINEID, PHASIC_TYPE);
|
||||
|
||||
CREATE INDEX idx_data_inharm_i_time_line_phase
|
||||
ON data_inharm_i (TIMEID, LINEID, PHASIC_TYPE);
|
||||
|
||||
CREATE INDEX idx_data_plt_time_line_phase
|
||||
ON data_plt (TIMEID, LINEID, PHASIC_TYPE);
|
||||
@@ -0,0 +1,42 @@
|
||||
-- 稳态模块菜单图标修正脚本。
|
||||
-- 本脚本不自动执行,请按数据库现状审阅后单独执行。
|
||||
|
||||
UPDATE sys_function
|
||||
SET Icon = 'DataAnalysis'
|
||||
WHERE State = 1
|
||||
AND Type = 0
|
||||
AND (
|
||||
Name = '稳态模块'
|
||||
OR Code IN ('steady', 'steadyModule', 'steadyDataView')
|
||||
OR Path IN ('/steady', '/steadyDataView', '/steady/data-view')
|
||||
);
|
||||
|
||||
UPDATE sys_function
|
||||
SET Icon = 'DataBoard'
|
||||
WHERE State = 1
|
||||
AND Type = 0
|
||||
AND (
|
||||
Name = '稳态数据'
|
||||
OR Code IN ('steadyData', 'steadyDataDetail')
|
||||
OR Path IN ('/steady/data', '/steady/data-view/detail', '/steadyDataView/index')
|
||||
);
|
||||
|
||||
UPDATE sys_function
|
||||
SET Icon = 'TrendCharts'
|
||||
WHERE State = 1
|
||||
AND Type = 0
|
||||
AND (
|
||||
Name = '稳态趋势'
|
||||
OR Code IN ('steadyTrend', 'steadyDataTrend')
|
||||
OR Path IN ('/steady/trend', '/steady/data-view/trend', '/steadyTrend/index')
|
||||
);
|
||||
|
||||
UPDATE sys_function
|
||||
SET Icon = 'CircleCheck'
|
||||
WHERE State = 1
|
||||
AND Type = 0
|
||||
AND (
|
||||
Name = '数据验证'
|
||||
OR Code IN ('dataValidation', 'steadyDataValidation')
|
||||
OR Path IN ('/steady/data-validation', '/steady/data-view/validation', '/dataValidation/index')
|
||||
);
|
||||
@@ -0,0 +1,147 @@
|
||||
package com.njcn.gather.steady.checksquare.component;
|
||||
|
||||
import com.njcn.gather.steady.checksquare.pojo.bo.SteadyChecksquareValuePointBO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityDetailVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityRuleVO;
|
||||
import com.njcn.gather.steady.datavie.component.SteadyTrendIndicatorCatalog;
|
||||
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendIndicatorDefinitionBO;
|
||||
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 谐波奇偶关系规则测试。
|
||||
*/
|
||||
class SteadyChecksquareHarmonicParityRuleComponentTest {
|
||||
|
||||
@Test
|
||||
void shouldRecordAbnormalWhenEvenHarmonicExceedsOddMedianThreshold() {
|
||||
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||
SteadyChecksquareHarmonicParityRuleComponent component = new SteadyChecksquareHarmonicParityRuleComponent(influxQueryComponent);
|
||||
LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0);
|
||||
when(influxQueryComponent.queryValuePointMap(any(),
|
||||
any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
|
||||
.thenAnswer(invocation -> {
|
||||
Map<String, List<SteadyChecksquareValuePointBO>> values = emptyBatchResult(invocation.getArgument(0));
|
||||
putPoint(values, "v_3", time, "10");
|
||||
putPoint(values, "v_4", time, "31");
|
||||
putPoint(values, "v_5", time, "12");
|
||||
putPoint(values, "v_7", time, "14");
|
||||
return values;
|
||||
});
|
||||
SteadyTrendIndicatorDefinitionBO indicator = new SteadyTrendIndicatorCatalog().getIndicator("V_HARMONIC");
|
||||
|
||||
SteadyChecksquareHarmonicParityRuleVO result = component.check("line-001", indicator,
|
||||
time, time, 1);
|
||||
|
||||
Assertions.assertEquals(Boolean.TRUE, result.getAbnormal());
|
||||
Assertions.assertEquals(Integer.valueOf(1), result.getAbnormalPointCount());
|
||||
SteadyChecksquareHarmonicParityDetailVO detail = result.getAbnormalDetails().get(0);
|
||||
Assertions.assertEquals("2026-05-01 00:00:00", detail.getTime());
|
||||
Assertions.assertEquals("A", detail.getPhase());
|
||||
Assertions.assertEquals("AVG", detail.getStatType());
|
||||
Assertions.assertEquals(Integer.valueOf(4), detail.getEvenHarmonicOrder());
|
||||
Assertions.assertEquals(new BigDecimal("31"), detail.getEvenValue());
|
||||
Assertions.assertEquals(Arrays.asList(3, 5, 7), detail.getOddHarmonicOrders());
|
||||
Assertions.assertEquals(new BigDecimal("12"), detail.getOddMedianValue());
|
||||
Assertions.assertEquals(new BigDecimal("2"), detail.getThresholdMultiplier());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipWhenOddReferenceCountLessThanTwo() {
|
||||
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||
SteadyChecksquareHarmonicParityRuleComponent component = new SteadyChecksquareHarmonicParityRuleComponent(influxQueryComponent);
|
||||
LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0);
|
||||
when(influxQueryComponent.queryValuePointMap(any(),
|
||||
any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
|
||||
.thenAnswer(invocation -> {
|
||||
Map<String, List<SteadyChecksquareValuePointBO>> values = emptyBatchResult(invocation.getArgument(0));
|
||||
putPoint(values, "v_2", time, "50");
|
||||
putPoint(values, "v_3", time, "10");
|
||||
return values;
|
||||
});
|
||||
SteadyTrendIndicatorDefinitionBO indicator = new SteadyTrendIndicatorCatalog().getIndicator("V_HARMONIC");
|
||||
|
||||
SteadyChecksquareHarmonicParityRuleVO result = component.check("line-001", indicator,
|
||||
time, time, 1);
|
||||
|
||||
Assertions.assertEquals(Boolean.FALSE, result.getAbnormal());
|
||||
Assertions.assertEquals(Integer.valueOf(0), result.getAbnormalPointCount());
|
||||
Assertions.assertTrue(result.getAbnormalDetails().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipEvenHarmonicWhenValueNotGreaterThanDeadband() {
|
||||
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||
SteadyChecksquareHarmonicParityRuleComponent component = new SteadyChecksquareHarmonicParityRuleComponent(influxQueryComponent);
|
||||
LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0);
|
||||
when(influxQueryComponent.queryValuePointMap(any(),
|
||||
any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
|
||||
.thenAnswer(invocation -> {
|
||||
Map<String, List<SteadyChecksquareValuePointBO>> values = emptyBatchResult(invocation.getArgument(0));
|
||||
putPoint(values, "v_3", time, "0.01");
|
||||
putPoint(values, "v_4", time, "0.10");
|
||||
putPoint(values, "v_5", time, "0.02");
|
||||
return values;
|
||||
});
|
||||
SteadyTrendIndicatorDefinitionBO indicator = new SteadyTrendIndicatorCatalog().getIndicator("V_HARMONIC");
|
||||
|
||||
SteadyChecksquareHarmonicParityRuleVO result = component.check("line-001", indicator,
|
||||
time, time, 1);
|
||||
|
||||
Assertions.assertEquals(Boolean.FALSE, result.getAbnormal());
|
||||
Assertions.assertEquals(Integer.valueOf(0), result.getAbnormalPointCount());
|
||||
Assertions.assertTrue(result.getAbnormalDetails().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipNonHarmonicIndicator() {
|
||||
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||
SteadyChecksquareHarmonicParityRuleComponent component = new SteadyChecksquareHarmonicParityRuleComponent(influxQueryComponent);
|
||||
SteadyTrendIndicatorDefinitionBO indicator = new SteadyTrendIndicatorCatalog().getIndicator("V_RMS");
|
||||
|
||||
SteadyChecksquareHarmonicParityRuleVO result = component.check("line-001", indicator,
|
||||
LocalDateTime.of(2026, 5, 1, 0, 0),
|
||||
LocalDateTime.of(2026, 5, 1, 0, 1), 1);
|
||||
|
||||
Assertions.assertEquals(Boolean.FALSE, result.getAbnormal());
|
||||
Assertions.assertEquals(Integer.valueOf(0), result.getAbnormalPointCount());
|
||||
Assertions.assertTrue(result.getAbnormalDetails().isEmpty());
|
||||
}
|
||||
|
||||
private SteadyChecksquareValuePointBO point(LocalDateTime time, String value) {
|
||||
SteadyChecksquareValuePointBO point = new SteadyChecksquareValuePointBO();
|
||||
point.setTime(time);
|
||||
point.setValue(new BigDecimal(value));
|
||||
return point;
|
||||
}
|
||||
|
||||
private Map<String, List<SteadyChecksquareValuePointBO>> emptyBatchResult(List<SteadyTrendResolvedFieldBO> fields) {
|
||||
Map<String, List<SteadyChecksquareValuePointBO>> result =
|
||||
new LinkedHashMap<String, List<SteadyChecksquareValuePointBO>>();
|
||||
for (SteadyTrendResolvedFieldBO field : fields) {
|
||||
result.put(field.getField(), Collections.<SteadyChecksquareValuePointBO>emptyList());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void putPoint(Map<String, List<SteadyChecksquareValuePointBO>> values, String field,
|
||||
LocalDateTime time, String value) {
|
||||
if (values.containsKey(field)) {
|
||||
values.put(field, Collections.singletonList(point(time, value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,16 @@ package com.njcn.gather.steady.checksquare.component;
|
||||
|
||||
import com.njcn.gather.steady.datavie.config.SteadyInfluxDbProperties;
|
||||
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* 数据校验 InfluxQL 构造契约测试。
|
||||
@@ -33,4 +39,115 @@ class SteadyChecksquareInfluxQueryComponentTest {
|
||||
Assertions.assertFalse(query.contains("quality_flag"));
|
||||
Assertions.assertFalse(query.contains("GROUP BY time"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBuildValuePointQueryWithStatTypeFilter() {
|
||||
SteadyChecksquareInfluxQueryComponent component = new SteadyChecksquareInfluxQueryComponent(new SteadyInfluxDbProperties());
|
||||
SteadyTrendResolvedFieldBO field = new SteadyTrendResolvedFieldBO();
|
||||
field.setMeasurement("data_v");
|
||||
field.setField("rms");
|
||||
field.setLineId("line-001");
|
||||
field.setPhase("A");
|
||||
field.setStatType("CP95");
|
||||
|
||||
String query = component.buildValuePointQuery(field,
|
||||
LocalDateTime.of(2026, 5, 1, 0, 0, 0),
|
||||
LocalDateTime.of(2026, 5, 1, 23, 59, 59));
|
||||
|
||||
Assertions.assertTrue(query.contains("SELECT \"rms\" AS \"value\""));
|
||||
Assertions.assertTrue(query.contains("\"line_id\" = 'line-001'"));
|
||||
Assertions.assertTrue(query.contains("\"phasic_type\" = 'A'"));
|
||||
Assertions.assertTrue(query.contains("\"value_type\" = 'CP95'"));
|
||||
Assertions.assertTrue(query.endsWith("ORDER BY time ASC"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReuseValuePointQueryWithinRequestCache() throws Exception {
|
||||
AtomicInteger requestCount = new AtomicInteger();
|
||||
HttpServer server = HttpServer.create(new InetSocketAddress(0), 0);
|
||||
server.createContext("/query", exchange -> {
|
||||
requestCount.incrementAndGet();
|
||||
byte[] body = ("{\"results\":[{\"series\":[{\"values\":["
|
||||
+ "[\"2026-05-01T00:00:00Z\",1.23],"
|
||||
+ "[\"2026-05-01T00:01:00Z\",2.34]"
|
||||
+ "]}]}]}").getBytes(StandardCharsets.UTF_8);
|
||||
exchange.sendResponseHeaders(200, body.length);
|
||||
exchange.getResponseBody().write(body);
|
||||
exchange.close();
|
||||
});
|
||||
server.start();
|
||||
try {
|
||||
SteadyInfluxDbProperties properties = new SteadyInfluxDbProperties();
|
||||
properties.setUrl("http://127.0.0.1:" + server.getAddress().getPort());
|
||||
properties.setDatabase("steady");
|
||||
SteadyChecksquareInfluxQueryComponent component = new SteadyChecksquareInfluxQueryComponent(properties);
|
||||
SteadyTrendResolvedFieldBO field = new SteadyTrendResolvedFieldBO();
|
||||
field.setMeasurement("data_v");
|
||||
field.setField("rms");
|
||||
field.setLineId("line-001");
|
||||
field.setPhase("A");
|
||||
field.setStatType("AVG");
|
||||
|
||||
component.enableRequestCache();
|
||||
component.queryExistingSlots(field,
|
||||
LocalDateTime.of(2026, 5, 1, 0, 0, 0),
|
||||
LocalDateTime.of(2026, 5, 1, 0, 1, 0), 1);
|
||||
component.queryValuePoints(field,
|
||||
LocalDateTime.of(2026, 5, 1, 0, 0, 0),
|
||||
LocalDateTime.of(2026, 5, 1, 0, 1, 0), 1);
|
||||
component.clearRequestCache();
|
||||
|
||||
Assertions.assertEquals(1, requestCount.get());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldQueryMultipleValueFieldsOnce() throws Exception {
|
||||
AtomicInteger requestCount = new AtomicInteger();
|
||||
HttpServer server = HttpServer.create(new InetSocketAddress(0), 0);
|
||||
server.createContext("/query", exchange -> {
|
||||
requestCount.incrementAndGet();
|
||||
byte[] body = ("{\"results\":[{\"series\":[{\"columns\":[\"time\",\"h_2\",\"h_3\"],\"values\":["
|
||||
+ "[\"2026-05-01T00:00:00Z\",1.23,2.34],"
|
||||
+ "[\"2026-05-01T00:01:00Z\",3.45,null]"
|
||||
+ "]}]}]}").getBytes(StandardCharsets.UTF_8);
|
||||
exchange.sendResponseHeaders(200, body.length);
|
||||
exchange.getResponseBody().write(body);
|
||||
exchange.close();
|
||||
});
|
||||
server.start();
|
||||
try {
|
||||
SteadyInfluxDbProperties properties = new SteadyInfluxDbProperties();
|
||||
properties.setUrl("http://127.0.0.1:" + server.getAddress().getPort());
|
||||
properties.setDatabase("steady");
|
||||
SteadyChecksquareInfluxQueryComponent component = new SteadyChecksquareInfluxQueryComponent(properties);
|
||||
SteadyTrendResolvedFieldBO h2 = buildField("h_2");
|
||||
SteadyTrendResolvedFieldBO h3 = buildField("h_3");
|
||||
|
||||
component.enableRequestCache();
|
||||
Map<String, java.util.List<com.njcn.gather.steady.checksquare.pojo.bo.SteadyChecksquareValuePointBO>> result =
|
||||
component.queryValuePointMap(Arrays.asList(h2, h3),
|
||||
LocalDateTime.of(2026, 5, 1, 0, 0, 0),
|
||||
LocalDateTime.of(2026, 5, 1, 0, 1, 0), 1);
|
||||
component.clearRequestCache();
|
||||
|
||||
Assertions.assertEquals(1, requestCount.get());
|
||||
Assertions.assertEquals(2, result.get("h_2").size());
|
||||
Assertions.assertEquals(1, result.get("h_3").size());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
private SteadyTrendResolvedFieldBO buildField(String fieldName) {
|
||||
SteadyTrendResolvedFieldBO field = new SteadyTrendResolvedFieldBO();
|
||||
field.setMeasurement("data_harmonic");
|
||||
field.setField(fieldName);
|
||||
field.setLineId("line-001");
|
||||
field.setPhase("A");
|
||||
field.setStatType("AVG");
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
package com.njcn.gather.steady.checksquare.component;
|
||||
|
||||
import com.njcn.gather.steady.checksquare.pojo.bo.SteadyChecksquareValuePointBO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareValueOrderRuleVO;
|
||||
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendIndicatorDefinitionBO;
|
||||
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendSeriesFieldBO;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 数据校验指标值大小关系规则测试。
|
||||
*/
|
||||
class SteadyChecksquareValueOrderRuleComponentTest {
|
||||
|
||||
@Test
|
||||
void shouldMarkIndicatorAbnormalWhenInvalidPointCountGreaterThanOne() {
|
||||
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||
SteadyChecksquareValueOrderRuleComponent component = new SteadyChecksquareValueOrderRuleComponent(influxQueryComponent);
|
||||
LocalDateTime firstTime = LocalDateTime.of(2026, 5, 1, 0, 0);
|
||||
LocalDateTime secondTime = LocalDateTime.of(2026, 5, 1, 0, 1);
|
||||
when(influxQueryComponent.queryValuePoints(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1)))
|
||||
.thenAnswer(invocation -> {
|
||||
String statType = invocation.getArgument(0, com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO.class).getStatType();
|
||||
if ("MAX".equals(statType)) {
|
||||
return Arrays.asList(point(firstTime, "8"), point(secondTime, "9"));
|
||||
}
|
||||
if ("CP95".equals(statType)) {
|
||||
return Arrays.asList(point(firstTime, "9"), point(secondTime, "10"));
|
||||
}
|
||||
if ("AVG".equals(statType)) {
|
||||
return Arrays.asList(point(firstTime, "7"), point(secondTime, "8"));
|
||||
}
|
||||
if ("MIN".equals(statType)) {
|
||||
return Arrays.asList(point(firstTime, "1"), point(secondTime, "9"));
|
||||
}
|
||||
return Collections.emptyList();
|
||||
});
|
||||
|
||||
SteadyChecksquareValueOrderRuleVO result = component.check("line-001", indicator(), null,
|
||||
LocalDateTime.of(2026, 5, 1, 0, 0), LocalDateTime.of(2026, 5, 1, 0, 2), 1);
|
||||
|
||||
Assertions.assertEquals(Integer.valueOf(2), result.getAbnormalPointCount());
|
||||
Assertions.assertEquals(Boolean.TRUE, result.getAbnormal());
|
||||
Assertions.assertEquals(2, result.getAbnormalDetails().size());
|
||||
Assertions.assertEquals("2026-05-01 00:00:00", result.getAbnormalDetails().get(0).getTime());
|
||||
Assertions.assertEquals("A", result.getAbnormalDetails().get(0).getPhase());
|
||||
Assertions.assertEquals(new BigDecimal("8"), result.getAbnormalDetails().get(0).getMaxValue());
|
||||
Assertions.assertEquals(new BigDecimal("1"), result.getAbnormalDetails().get(0).getMinValue());
|
||||
Assertions.assertEquals(new BigDecimal("7"), result.getAbnormalDetails().get(0).getAvgValue());
|
||||
Assertions.assertEquals(new BigDecimal("9"), result.getAbnormalDetails().get(0).getCp95Value());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTreatEqualAdjacentStatValuesAsNormal() {
|
||||
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||
SteadyChecksquareValueOrderRuleComponent component = new SteadyChecksquareValueOrderRuleComponent(influxQueryComponent);
|
||||
LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0);
|
||||
when(influxQueryComponent.queryValuePoints(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1)))
|
||||
.thenAnswer(invocation -> {
|
||||
String statType = invocation.getArgument(0, com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO.class).getStatType();
|
||||
if ("MAX".equals(statType)) {
|
||||
return Collections.singletonList(point(time, "10"));
|
||||
}
|
||||
if ("CP95".equals(statType)) {
|
||||
return Collections.singletonList(point(time, "10"));
|
||||
}
|
||||
if ("AVG".equals(statType)) {
|
||||
return Collections.singletonList(point(time, "8"));
|
||||
}
|
||||
if ("MIN".equals(statType)) {
|
||||
return Collections.singletonList(point(time, "8"));
|
||||
}
|
||||
return Collections.emptyList();
|
||||
});
|
||||
|
||||
SteadyChecksquareValueOrderRuleVO result = component.check("line-001", indicator(), null,
|
||||
LocalDateTime.of(2026, 5, 1, 0, 0), LocalDateTime.of(2026, 5, 1, 0, 1), 1);
|
||||
|
||||
Assertions.assertEquals(Integer.valueOf(0), result.getAbnormalPointCount());
|
||||
Assertions.assertEquals(Boolean.FALSE, result.getAbnormal());
|
||||
Assertions.assertTrue(result.getAbnormalDetails().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotMarkIndicatorAbnormalWhenOnlyOneInvalidPointExists() {
|
||||
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||
SteadyChecksquareValueOrderRuleComponent component = new SteadyChecksquareValueOrderRuleComponent(influxQueryComponent);
|
||||
LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0);
|
||||
when(influxQueryComponent.queryValuePoints(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1)))
|
||||
.thenAnswer(invocation -> {
|
||||
String statType = invocation.getArgument(0, com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO.class).getStatType();
|
||||
if ("MAX".equals(statType)) {
|
||||
return Collections.singletonList(point(time, "8"));
|
||||
}
|
||||
if ("CP95".equals(statType)) {
|
||||
return Collections.singletonList(point(time, "10"));
|
||||
}
|
||||
if ("AVG".equals(statType)) {
|
||||
return Collections.singletonList(point(time, "8"));
|
||||
}
|
||||
if ("MIN".equals(statType)) {
|
||||
return Collections.singletonList(point(time, "1"));
|
||||
}
|
||||
return Collections.emptyList();
|
||||
});
|
||||
|
||||
SteadyChecksquareValueOrderRuleVO result = component.check("line-001", indicator(), null,
|
||||
LocalDateTime.of(2026, 5, 1, 0, 0), LocalDateTime.of(2026, 5, 1, 0, 1), 1);
|
||||
|
||||
Assertions.assertEquals(Integer.valueOf(1), result.getAbnormalPointCount());
|
||||
Assertions.assertEquals(Boolean.FALSE, result.getAbnormal());
|
||||
Assertions.assertEquals(1, result.getAbnormalDetails().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFillHarmonicOrderInAbnormalDetailForHarmonicIndicator() {
|
||||
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||
SteadyChecksquareValueOrderRuleComponent component = new SteadyChecksquareValueOrderRuleComponent(influxQueryComponent);
|
||||
LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0);
|
||||
when(influxQueryComponent.queryValuePoints(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1)))
|
||||
.thenAnswer(invocation -> {
|
||||
String statType = invocation.getArgument(0, com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO.class).getStatType();
|
||||
if ("MAX".equals(statType)) {
|
||||
return Collections.singletonList(point(time, "8"));
|
||||
}
|
||||
if ("CP95".equals(statType)) {
|
||||
return Collections.singletonList(point(time, "10"));
|
||||
}
|
||||
if ("AVG".equals(statType)) {
|
||||
return Collections.singletonList(point(time, "8"));
|
||||
}
|
||||
if ("MIN".equals(statType)) {
|
||||
return Collections.singletonList(point(time, "1"));
|
||||
}
|
||||
return Collections.emptyList();
|
||||
});
|
||||
SteadyTrendIndicatorDefinitionBO indicator = indicator();
|
||||
indicator.setHarmonic(true);
|
||||
indicator.setHarmonicFieldPrefix("v");
|
||||
|
||||
SteadyChecksquareValueOrderRuleVO result = component.check("line-001", indicator, 5,
|
||||
LocalDateTime.of(2026, 5, 1, 0, 0), LocalDateTime.of(2026, 5, 1, 0, 1), 1);
|
||||
|
||||
Assertions.assertEquals(1, result.getAbnormalDetails().size());
|
||||
Assertions.assertEquals(Integer.valueOf(5), result.getAbnormalDetails().get(0).getHarmonicOrder());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipPointWhenAnyRequiredStatValueMissing() {
|
||||
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||
SteadyChecksquareValueOrderRuleComponent component = new SteadyChecksquareValueOrderRuleComponent(influxQueryComponent);
|
||||
LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0);
|
||||
when(influxQueryComponent.queryValuePoints(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1)))
|
||||
.thenAnswer(invocation -> {
|
||||
String statType = invocation.getArgument(0, com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO.class).getStatType();
|
||||
if ("MAX".equals(statType)) {
|
||||
return Collections.singletonList(point(time, "10"));
|
||||
}
|
||||
if ("CP95".equals(statType)) {
|
||||
return Collections.singletonList(point(time, "11"));
|
||||
}
|
||||
if ("MIN".equals(statType)) {
|
||||
return Collections.singletonList(point(time, "1"));
|
||||
}
|
||||
return Collections.emptyList();
|
||||
});
|
||||
|
||||
SteadyChecksquareValueOrderRuleVO result = component.check("line-001", indicator(), null,
|
||||
LocalDateTime.of(2026, 5, 1, 0, 0), LocalDateTime.of(2026, 5, 1, 0, 1), 1);
|
||||
|
||||
Assertions.assertEquals(Integer.valueOf(0), result.getAbnormalPointCount());
|
||||
Assertions.assertEquals(Boolean.FALSE, result.getAbnormal());
|
||||
Assertions.assertTrue(result.getAbnormalDetails().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipIndicatorWhenNotAllFourStatsSupported() {
|
||||
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||
SteadyChecksquareValueOrderRuleComponent component = new SteadyChecksquareValueOrderRuleComponent(influxQueryComponent);
|
||||
SteadyTrendIndicatorDefinitionBO indicator = indicator();
|
||||
indicator.setSupportStats(Collections.singletonList("AVG"));
|
||||
|
||||
SteadyChecksquareValueOrderRuleVO result = component.check("line-001", indicator, null,
|
||||
LocalDateTime.of(2026, 5, 1, 0, 0), LocalDateTime.of(2026, 5, 1, 0, 1), 1);
|
||||
|
||||
Assertions.assertEquals(Integer.valueOf(0), result.getAbnormalPointCount());
|
||||
Assertions.assertEquals(Boolean.FALSE, result.getAbnormal());
|
||||
Assertions.assertTrue(result.getAbnormalDetails().isEmpty());
|
||||
}
|
||||
|
||||
private SteadyTrendIndicatorDefinitionBO indicator() {
|
||||
SteadyTrendIndicatorDefinitionBO indicator = new SteadyTrendIndicatorDefinitionBO();
|
||||
indicator.setIndicatorCode("V_RMS");
|
||||
indicator.setName("相电压有效值");
|
||||
indicator.setTableName("data_v");
|
||||
indicator.setPhaseCodes(Collections.singletonList("A"));
|
||||
indicator.setSeriesFields(Collections.singletonList(new SteadyTrendSeriesFieldBO("rms", "相电压有效值")));
|
||||
indicator.setSupportStats(Arrays.asList("AVG", "MAX", "MIN", "CP95"));
|
||||
indicator.setUnit("V");
|
||||
return indicator;
|
||||
}
|
||||
|
||||
private SteadyChecksquareValuePointBO point(LocalDateTime time, String value) {
|
||||
SteadyChecksquareValuePointBO point = new SteadyChecksquareValuePointBO();
|
||||
point.setTime(time);
|
||||
point.setValue(new BigDecimal(value));
|
||||
return point;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.njcn.gather.steady.checksquare.controller;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@@ -17,8 +18,21 @@ class SteadyChecksquareControllerTest {
|
||||
RequestMapping requestMapping = SteadyChecksquareController.class.getAnnotation(RequestMapping.class);
|
||||
Assertions.assertArrayEquals(new String[]{"/steady/data-view/checksquare"}, requestMapping.value());
|
||||
|
||||
Method method = SteadyChecksquareController.class.getDeclaredMethod("query", com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam.class);
|
||||
PostMapping postMapping = method.getAnnotation(PostMapping.class);
|
||||
Assertions.assertArrayEquals(new String[]{"/query"}, postMapping.value());
|
||||
Method queryMethod = SteadyChecksquareController.class.getDeclaredMethod("query", com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareHistoryQueryParam.class);
|
||||
PostMapping queryMapping = queryMethod.getAnnotation(PostMapping.class);
|
||||
Assertions.assertArrayEquals(new String[]{"/query"}, queryMapping.value());
|
||||
|
||||
Method createMethod = SteadyChecksquareController.class.getDeclaredMethod("create", com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam.class);
|
||||
PostMapping createMapping = createMethod.getAnnotation(PostMapping.class);
|
||||
Assertions.assertArrayEquals(new String[]{"/create"}, createMapping.value());
|
||||
|
||||
Method detailMethod = SteadyChecksquareController.class.getDeclaredMethod("detail", String.class);
|
||||
GetMapping detailMapping = detailMethod.getAnnotation(GetMapping.class);
|
||||
Assertions.assertArrayEquals(new String[]{"/detail"}, detailMapping.value());
|
||||
|
||||
Method itemDetailMethod = SteadyChecksquareController.class.getDeclaredMethod("itemDetail",
|
||||
String.class, String.class, String.class, Integer.class, Integer.class);
|
||||
GetMapping itemDetailMapping = itemDetailMethod.getAnnotation(GetMapping.class);
|
||||
Assertions.assertArrayEquals(new String[]{"/item-detail"}, itemDetailMapping.value());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class SteadyChecksquareQueryParamTest {
|
||||
Assertions.assertNull(field("qualityFlag"));
|
||||
Assertions.assertNull(field("statTypes"));
|
||||
Assertions.assertNull(field("phases"));
|
||||
Assertions.assertNotNull(field("harmonicOrders"));
|
||||
Assertions.assertNull(field("harmonicOrders"));
|
||||
}
|
||||
|
||||
private Field field(String name) {
|
||||
|
||||
@@ -1,26 +1,54 @@
|
||||
package com.njcn.gather.steady.checksquare.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.njcn.gather.steady.checksquare.component.SteadyChecksquareCalculator;
|
||||
import com.njcn.gather.steady.checksquare.component.SteadyChecksquareHarmonicParityRuleComponent;
|
||||
import com.njcn.gather.steady.checksquare.component.SteadyChecksquareInfluxQueryComponent;
|
||||
import com.njcn.gather.steady.checksquare.component.SteadyChecksquareValueOrderRuleComponent;
|
||||
import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam;
|
||||
import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareDetailPO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareItemPO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareStatSummaryPO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareTaskPO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityDetailVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityRuleVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemDetailVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareQueryVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareStatSummaryVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareValueOrderDetailVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareValueOrderRuleVO;
|
||||
import com.njcn.gather.steady.checksquare.service.SteadyChecksquareDetailService;
|
||||
import com.njcn.gather.steady.checksquare.service.SteadyChecksquareItemService;
|
||||
import com.njcn.gather.steady.checksquare.service.SteadyChecksquareStatSummaryService;
|
||||
import com.njcn.gather.steady.checksquare.service.SteadyChecksquareTaskService;
|
||||
import com.njcn.gather.steady.datavie.component.SteadyTrendIndicatorCatalog;
|
||||
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO;
|
||||
import com.njcn.gather.tool.adddata.component.AddDataTimeSlotCalculator;
|
||||
import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerLinePathVO;
|
||||
import com.njcn.gather.tool.addledger.service.AddLedgerService;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
@@ -28,12 +56,28 @@ import static org.mockito.Mockito.when;
|
||||
*/
|
||||
class SteadyChecksquareServiceImplTest {
|
||||
|
||||
@Test
|
||||
void shouldNotOpenTransactionAroundCreateCalculation() throws Exception {
|
||||
Method createMethod = SteadyChecksquareServiceImpl.class.getMethod("create", SteadyChecksquareQueryParam.class);
|
||||
|
||||
Assertions.assertNull(createMethod.getAnnotation(Transactional.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseFixedFlickerIntervalsPerIndicator() {
|
||||
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||
SteadyChecksquareValueOrderRuleComponent valueOrderRuleComponent = mock(SteadyChecksquareValueOrderRuleComponent.class);
|
||||
SteadyChecksquareHarmonicParityRuleComponent harmonicParityRuleComponent = mock(SteadyChecksquareHarmonicParityRuleComponent.class);
|
||||
AddLedgerService addLedgerService = mock(AddLedgerService.class);
|
||||
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
|
||||
influxQueryComponent, new SteadyChecksquareCalculator(), new AddDataTimeSlotCalculator(), addLedgerService);
|
||||
influxQueryComponent, new SteadyChecksquareCalculator(), valueOrderRuleComponent, harmonicParityRuleComponent,
|
||||
new AddDataTimeSlotCalculator(), addLedgerService, mock(SteadyChecksquareTaskService.class),
|
||||
mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class),
|
||||
mock(SteadyChecksquareDetailService.class), new ObjectMapper());
|
||||
when(valueOrderRuleComponent.check(any(), any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
|
||||
.thenReturn(emptyRuleResult());
|
||||
when(harmonicParityRuleComponent.check(any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
|
||||
.thenReturn(emptyHarmonicParityRuleResult());
|
||||
AddLedgerLinePathVO linePath = new AddLedgerLinePathVO();
|
||||
linePath.setLineId("line-001");
|
||||
linePath.setLineName("进线一");
|
||||
@@ -54,7 +98,7 @@ class SteadyChecksquareServiceImplTest {
|
||||
param.setTimeStart("2026-05-01 00:00:00");
|
||||
param.setTimeEnd("2026-05-01 02:00:00");
|
||||
|
||||
SteadyChecksquareQueryVO result = service.query(param);
|
||||
SteadyChecksquareQueryVO result = calculate(service, param);
|
||||
|
||||
Assertions.assertEquals(Integer.valueOf(1), result.getIntervalMinutes());
|
||||
Assertions.assertEquals(3, result.getItems().size());
|
||||
@@ -64,11 +108,20 @@ class SteadyChecksquareServiceImplTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldOnlyQueryRequestedHarmonicOrders() {
|
||||
void shouldAggregateAllHarmonicOrdersIntoIndicatorItem() {
|
||||
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||
SteadyChecksquareValueOrderRuleComponent valueOrderRuleComponent = mock(SteadyChecksquareValueOrderRuleComponent.class);
|
||||
SteadyChecksquareHarmonicParityRuleComponent harmonicParityRuleComponent = mock(SteadyChecksquareHarmonicParityRuleComponent.class);
|
||||
AddLedgerService addLedgerService = mock(AddLedgerService.class);
|
||||
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
|
||||
influxQueryComponent, new SteadyChecksquareCalculator(), new AddDataTimeSlotCalculator(), addLedgerService);
|
||||
influxQueryComponent, new SteadyChecksquareCalculator(), valueOrderRuleComponent, harmonicParityRuleComponent,
|
||||
new AddDataTimeSlotCalculator(), addLedgerService, mock(SteadyChecksquareTaskService.class),
|
||||
mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class),
|
||||
mock(SteadyChecksquareDetailService.class), new ObjectMapper());
|
||||
when(valueOrderRuleComponent.check(any(), any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
|
||||
.thenReturn(emptyRuleResult());
|
||||
when(harmonicParityRuleComponent.check(any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
|
||||
.thenReturn(emptyHarmonicParityRuleResult());
|
||||
AddLedgerLinePathVO linePath = new AddLedgerLinePathVO();
|
||||
linePath.setLineId("line-001");
|
||||
linePath.setLineName("进线一");
|
||||
@@ -82,22 +135,32 @@ class SteadyChecksquareServiceImplTest {
|
||||
SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam();
|
||||
param.setLineId("line-001");
|
||||
param.setIndicatorCodes(Collections.singletonList("V_HARMONIC"));
|
||||
param.setHarmonicOrders(Collections.singletonList(5));
|
||||
param.setTimeStart("2026-05-01 00:00:00");
|
||||
param.setTimeEnd("2026-05-01 00:01:00");
|
||||
|
||||
SteadyChecksquareQueryVO result = service.query(param);
|
||||
SteadyChecksquareQueryVO result = calculate(service, param);
|
||||
|
||||
Assertions.assertEquals(1, result.getItems().size());
|
||||
Assertions.assertEquals(Integer.valueOf(5), result.getItems().get(0).getHarmonicOrder());
|
||||
Assertions.assertEquals("line-001|V_HARMONIC", result.getItems().get(0).getItemKey());
|
||||
Assertions.assertNull(result.getItems().get(0).getHarmonicOrder());
|
||||
Assertions.assertEquals(Integer.valueOf(2), result.getItems().get(0).getStatDetails().get(0).getSegments().get(0).getHarmonicOrder());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldKeepRequestedHarmonicOrdersDistinctAndOrdered() {
|
||||
void shouldAverageHarmonicOrderResultsAndMarkAbnormalWhenAnyOrderAbnormal() {
|
||||
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||
SteadyChecksquareValueOrderRuleComponent valueOrderRuleComponent = mock(SteadyChecksquareValueOrderRuleComponent.class);
|
||||
SteadyChecksquareHarmonicParityRuleComponent harmonicParityRuleComponent = mock(SteadyChecksquareHarmonicParityRuleComponent.class);
|
||||
AddLedgerService addLedgerService = mock(AddLedgerService.class);
|
||||
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
|
||||
influxQueryComponent, new SteadyChecksquareCalculator(), new AddDataTimeSlotCalculator(), addLedgerService);
|
||||
influxQueryComponent, new SteadyChecksquareCalculator(), valueOrderRuleComponent, harmonicParityRuleComponent,
|
||||
new AddDataTimeSlotCalculator(), addLedgerService, mock(SteadyChecksquareTaskService.class),
|
||||
mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class),
|
||||
mock(SteadyChecksquareDetailService.class), new ObjectMapper());
|
||||
when(valueOrderRuleComponent.check(any(), any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
|
||||
.thenReturn(emptyRuleResult());
|
||||
when(harmonicParityRuleComponent.check(any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
|
||||
.thenReturn(emptyHarmonicParityRuleResult());
|
||||
AddLedgerLinePathVO linePath = new AddLedgerLinePathVO();
|
||||
linePath.setLineId("line-001");
|
||||
linePath.setLineName("进线一");
|
||||
@@ -105,21 +168,328 @@ class SteadyChecksquareServiceImplTest {
|
||||
when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001"))))
|
||||
.thenReturn(Collections.singletonMap("line-001", linePath));
|
||||
when(influxQueryComponent.queryExistingSlots(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1)))
|
||||
.thenReturn(new HashSet<LocalDateTime>());
|
||||
.thenReturn(new HashSet<LocalDateTime>(Collections.singletonList(
|
||||
LocalDateTime.of(2026, 5, 1, 0, 0))));
|
||||
SteadyChecksquareValueOrderRuleVO normalRuleResult = new SteadyChecksquareValueOrderRuleVO();
|
||||
SteadyChecksquareValueOrderRuleVO abnormalRuleResult = new SteadyChecksquareValueOrderRuleVO();
|
||||
SteadyChecksquareValueOrderDetailVO abnormalDetail = new SteadyChecksquareValueOrderDetailVO();
|
||||
abnormalDetail.setTime("2026-05-01 00:00:00");
|
||||
abnormalDetail.setPhase("A");
|
||||
abnormalDetail.setHarmonicOrder(2);
|
||||
abnormalRuleResult.setAbnormal(true);
|
||||
abnormalRuleResult.setAbnormalPointCount(4);
|
||||
abnormalRuleResult.setAbnormalDetails(Collections.singletonList(abnormalDetail));
|
||||
when(valueOrderRuleComponent.check(any(), any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
|
||||
.thenReturn(normalRuleResult);
|
||||
when(valueOrderRuleComponent.check(any(), any(), eq(2), any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
|
||||
.thenReturn(abnormalRuleResult);
|
||||
when(valueOrderRuleComponent.check(any(), any(), eq(3), any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
|
||||
.thenReturn(normalRuleResult);
|
||||
|
||||
SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam();
|
||||
param.setLineId("line-001");
|
||||
param.setIndicatorCodes(Collections.singletonList("V_HARMONIC"));
|
||||
param.setHarmonicOrders(Arrays.asList(7, 5, 7));
|
||||
param.setTimeStart("2026-05-01 00:00:00");
|
||||
param.setTimeEnd("2026-05-01 00:01:00");
|
||||
|
||||
SteadyChecksquareQueryVO result = service.query(param);
|
||||
SteadyChecksquareQueryVO result = calculate(service, param);
|
||||
|
||||
List<SteadyChecksquareItemVO> items = result.getItems();
|
||||
Assertions.assertEquals(2, items.size());
|
||||
Assertions.assertEquals(Integer.valueOf(7), items.get(0).getHarmonicOrder());
|
||||
Assertions.assertEquals(Integer.valueOf(5), items.get(1).getHarmonicOrder());
|
||||
Assertions.assertEquals(1, items.size());
|
||||
Assertions.assertEquals(Boolean.TRUE, items.get(0).getAbnormal());
|
||||
Assertions.assertEquals(Integer.valueOf(1), items.get(0).getAbnormalPointCount());
|
||||
Assertions.assertEquals(1, items.get(0).getAbnormalDetails().size());
|
||||
Assertions.assertEquals(Integer.valueOf(2), items.get(0).getAbnormalDetails().get(0).getHarmonicOrder());
|
||||
Assertions.assertEquals(Integer.valueOf(8), items.get(0).getExpectedPointCount());
|
||||
Assertions.assertEquals(Integer.valueOf(4), items.get(0).getActualPointCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAssembleValueOrderRuleResultIntoItem() {
|
||||
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||
SteadyChecksquareValueOrderRuleComponent valueOrderRuleComponent = mock(SteadyChecksquareValueOrderRuleComponent.class);
|
||||
SteadyChecksquareHarmonicParityRuleComponent harmonicParityRuleComponent = mock(SteadyChecksquareHarmonicParityRuleComponent.class);
|
||||
AddLedgerService addLedgerService = mock(AddLedgerService.class);
|
||||
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
|
||||
influxQueryComponent, new SteadyChecksquareCalculator(), valueOrderRuleComponent, harmonicParityRuleComponent,
|
||||
new AddDataTimeSlotCalculator(), addLedgerService, mock(SteadyChecksquareTaskService.class),
|
||||
mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class),
|
||||
mock(SteadyChecksquareDetailService.class), new ObjectMapper());
|
||||
AddLedgerLinePathVO linePath = new AddLedgerLinePathVO();
|
||||
linePath.setLineId("line-001");
|
||||
linePath.setLineName("进线一");
|
||||
linePath.setLineInterval(1);
|
||||
when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001"))))
|
||||
.thenReturn(Collections.singletonMap("line-001", linePath));
|
||||
when(influxQueryComponent.queryExistingSlots(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1)))
|
||||
.thenReturn(new HashSet<LocalDateTime>(Collections.singletonList(
|
||||
LocalDateTime.of(2026, 5, 1, 0, 0))));
|
||||
SteadyChecksquareValueOrderRuleVO ruleResult = new SteadyChecksquareValueOrderRuleVO();
|
||||
SteadyChecksquareValueOrderDetailVO detail = new SteadyChecksquareValueOrderDetailVO();
|
||||
detail.setTime("2026-05-01 00:00:00");
|
||||
detail.setPhase("A");
|
||||
ruleResult.setAbnormalPointCount(2);
|
||||
ruleResult.setAbnormal(true);
|
||||
ruleResult.setAbnormalDetails(Collections.singletonList(detail));
|
||||
when(valueOrderRuleComponent.check(any(), any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1)))
|
||||
.thenReturn(ruleResult);
|
||||
|
||||
SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam();
|
||||
param.setLineId("line-001");
|
||||
param.setIndicatorCodes(Collections.singletonList("V_RMS"));
|
||||
param.setTimeStart("2026-05-01 00:00:00");
|
||||
param.setTimeEnd("2026-05-01 00:01:00");
|
||||
|
||||
SteadyChecksquareQueryVO result = calculate(service, param);
|
||||
|
||||
SteadyChecksquareItemVO item = result.getItems().get(0);
|
||||
Assertions.assertEquals(Boolean.TRUE, item.getAbnormal());
|
||||
Assertions.assertEquals(Integer.valueOf(2), item.getAbnormalPointCount());
|
||||
Assertions.assertEquals(1, item.getAbnormalDetails().size());
|
||||
Assertions.assertEquals("A", item.getAbnormalDetails().get(0).getPhase());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPrefetchNormalIndicatorFieldsByMeasurementPhaseAndStat() {
|
||||
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||
SteadyChecksquareValueOrderRuleComponent valueOrderRuleComponent = mock(SteadyChecksquareValueOrderRuleComponent.class);
|
||||
SteadyChecksquareHarmonicParityRuleComponent harmonicParityRuleComponent = mock(SteadyChecksquareHarmonicParityRuleComponent.class);
|
||||
AddLedgerService addLedgerService = mock(AddLedgerService.class);
|
||||
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
|
||||
influxQueryComponent, new SteadyChecksquareCalculator(), valueOrderRuleComponent, harmonicParityRuleComponent,
|
||||
new AddDataTimeSlotCalculator(), addLedgerService, mock(SteadyChecksquareTaskService.class),
|
||||
mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class),
|
||||
mock(SteadyChecksquareDetailService.class), new ObjectMapper());
|
||||
when(valueOrderRuleComponent.check(any(), any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
|
||||
.thenReturn(emptyRuleResult());
|
||||
when(harmonicParityRuleComponent.check(any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
|
||||
.thenReturn(emptyHarmonicParityRuleResult());
|
||||
AddLedgerLinePathVO linePath = new AddLedgerLinePathVO();
|
||||
linePath.setLineId("line-001");
|
||||
linePath.setLineName("进线一");
|
||||
linePath.setLineInterval(1);
|
||||
when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001"))))
|
||||
.thenReturn(Collections.singletonMap("line-001", linePath));
|
||||
when(influxQueryComponent.queryExistingSlots(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1)))
|
||||
.thenReturn(new HashSet<LocalDateTime>(Collections.singletonList(
|
||||
LocalDateTime.of(2026, 5, 1, 0, 0))));
|
||||
|
||||
SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam();
|
||||
param.setLineId("line-001");
|
||||
param.setIndicatorCodes(Arrays.asList("V_RMS", "V_LINE_RMS"));
|
||||
param.setTimeStart("2026-05-01 00:00:00");
|
||||
param.setTimeEnd("2026-05-01 00:01:00");
|
||||
|
||||
calculate(service, param);
|
||||
|
||||
ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
|
||||
verify(influxQueryComponent, times(12)).queryValuePointMap(captor.capture(),
|
||||
any(LocalDateTime.class), any(LocalDateTime.class), eq(1));
|
||||
boolean foundBatch = false;
|
||||
for (List fields : captor.getAllValues()) {
|
||||
if (fields.size() == 2) {
|
||||
List<String> fieldNames = new ArrayList<String>();
|
||||
for (Object field : fields) {
|
||||
fieldNames.add(((SteadyTrendResolvedFieldBO) field).getField());
|
||||
}
|
||||
foundBatch = fieldNames.contains("rms") && fieldNames.contains("rms_lvr");
|
||||
}
|
||||
}
|
||||
Assertions.assertTrue(foundBatch);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAssembleHarmonicParityRuleResultIntoAggregateItem() {
|
||||
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||
SteadyChecksquareValueOrderRuleComponent valueOrderRuleComponent = mock(SteadyChecksquareValueOrderRuleComponent.class);
|
||||
SteadyChecksquareHarmonicParityRuleComponent harmonicParityRuleComponent = mock(SteadyChecksquareHarmonicParityRuleComponent.class);
|
||||
AddLedgerService addLedgerService = mock(AddLedgerService.class);
|
||||
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
|
||||
influxQueryComponent, new SteadyChecksquareCalculator(), valueOrderRuleComponent, harmonicParityRuleComponent,
|
||||
new AddDataTimeSlotCalculator(), addLedgerService, mock(SteadyChecksquareTaskService.class),
|
||||
mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class),
|
||||
mock(SteadyChecksquareDetailService.class), new ObjectMapper());
|
||||
when(valueOrderRuleComponent.check(any(), any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
|
||||
.thenReturn(emptyRuleResult());
|
||||
AddLedgerLinePathVO linePath = new AddLedgerLinePathVO();
|
||||
linePath.setLineId("line-001");
|
||||
linePath.setLineName("进线一");
|
||||
linePath.setLineInterval(1);
|
||||
when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001"))))
|
||||
.thenReturn(Collections.singletonMap("line-001", linePath));
|
||||
when(influxQueryComponent.queryExistingSlots(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1)))
|
||||
.thenReturn(new HashSet<LocalDateTime>(Collections.singletonList(
|
||||
LocalDateTime.of(2026, 5, 1, 0, 0))));
|
||||
SteadyChecksquareHarmonicParityRuleVO ruleResult = new SteadyChecksquareHarmonicParityRuleVO();
|
||||
SteadyChecksquareHarmonicParityDetailVO detail = new SteadyChecksquareHarmonicParityDetailVO();
|
||||
detail.setTime("2026-05-01 00:00:00");
|
||||
detail.setPhase("A");
|
||||
detail.setStatType("AVG");
|
||||
detail.setEvenHarmonicOrder(4);
|
||||
ruleResult.setAbnormal(true);
|
||||
ruleResult.setAbnormalPointCount(1);
|
||||
ruleResult.setAbnormalDetails(Collections.singletonList(detail));
|
||||
when(harmonicParityRuleComponent.check(any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1)))
|
||||
.thenReturn(ruleResult);
|
||||
|
||||
SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam();
|
||||
param.setLineId("line-001");
|
||||
param.setIndicatorCodes(Collections.singletonList("V_HARMONIC"));
|
||||
param.setTimeStart("2026-05-01 00:00:00");
|
||||
param.setTimeEnd("2026-05-01 00:01:00");
|
||||
|
||||
SteadyChecksquareQueryVO result = calculate(service, param);
|
||||
|
||||
SteadyChecksquareItemVO item = result.getItems().get(0);
|
||||
Assertions.assertNull(item.getHarmonicOrder());
|
||||
Assertions.assertEquals(Boolean.TRUE, item.getHarmonicParityAbnormal());
|
||||
Assertions.assertEquals(Integer.valueOf(1), item.getHarmonicParityAbnormalPointCount());
|
||||
Assertions.assertEquals("AVG", item.getHarmonicParityAbnormalDetails().get(0).getStatType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectUnsupportedItemDetailType() {
|
||||
SteadyChecksquareItemService itemService = mock(SteadyChecksquareItemService.class);
|
||||
SteadyChecksquareItemPO item = new SteadyChecksquareItemPO();
|
||||
item.setId("item-001");
|
||||
item.setState(1);
|
||||
when(itemService.getById("item-001")).thenReturn(item);
|
||||
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
|
||||
mock(SteadyChecksquareInfluxQueryComponent.class), new SteadyChecksquareCalculator(),
|
||||
mock(SteadyChecksquareValueOrderRuleComponent.class), mock(SteadyChecksquareHarmonicParityRuleComponent.class),
|
||||
new AddDataTimeSlotCalculator(), mock(AddLedgerService.class), mock(SteadyChecksquareTaskService.class),
|
||||
itemService, mock(SteadyChecksquareStatSummaryService.class),
|
||||
mock(SteadyChecksquareDetailService.class), new ObjectMapper());
|
||||
|
||||
Assertions.assertThrows(RuntimeException.class, () -> service.itemDetail("item-001", "UNKNOWN", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldLoadDetailSummariesInSingleBatch() {
|
||||
SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class);
|
||||
SteadyChecksquareItemService itemService = mock(SteadyChecksquareItemService.class);
|
||||
SteadyChecksquareStatSummaryService statSummaryService = mock(SteadyChecksquareStatSummaryService.class);
|
||||
LambdaQueryChainWrapper<SteadyChecksquareItemPO> itemQuery = mock(LambdaQueryChainWrapper.class);
|
||||
LambdaQueryChainWrapper<SteadyChecksquareStatSummaryPO> summaryQuery = mock(LambdaQueryChainWrapper.class);
|
||||
SteadyChecksquareTaskPO task = new SteadyChecksquareTaskPO();
|
||||
task.setId("task-001");
|
||||
task.setState(1);
|
||||
task.setLineId("line-001");
|
||||
task.setLineName("进线一");
|
||||
task.setTimeStart(LocalDateTime.of(2026, 5, 1, 0, 0));
|
||||
task.setTimeEnd(LocalDateTime.of(2026, 5, 1, 0, 1));
|
||||
task.setIntervalMinutes(1);
|
||||
SteadyChecksquareItemPO item1 = buildItemPO("item-001", "V_RMS");
|
||||
SteadyChecksquareItemPO item2 = buildItemPO("item-002", "FREQ");
|
||||
SteadyChecksquareStatSummaryPO summary1 = buildSummaryPO("item-001", "AVG");
|
||||
SteadyChecksquareStatSummaryPO summary2 = buildSummaryPO("item-002", "AVG");
|
||||
when(taskService.getById("task-001")).thenReturn(task);
|
||||
when(itemService.lambdaQuery()).thenReturn(itemQuery);
|
||||
when(itemQuery.eq(any(), any())).thenReturn(itemQuery);
|
||||
when(itemQuery.list()).thenReturn(Arrays.asList(item1, item2));
|
||||
when(statSummaryService.lambdaQuery()).thenReturn(summaryQuery);
|
||||
when(summaryQuery.in(any(), any(List.class))).thenReturn(summaryQuery);
|
||||
when(summaryQuery.list()).thenReturn(Arrays.asList(summary1, summary2));
|
||||
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
|
||||
mock(SteadyChecksquareInfluxQueryComponent.class), new SteadyChecksquareCalculator(),
|
||||
mock(SteadyChecksquareValueOrderRuleComponent.class), mock(SteadyChecksquareHarmonicParityRuleComponent.class),
|
||||
new AddDataTimeSlotCalculator(), mock(AddLedgerService.class), taskService,
|
||||
itemService, statSummaryService, mock(SteadyChecksquareDetailService.class), new ObjectMapper());
|
||||
|
||||
SteadyChecksquareQueryVO result = service.detail("task-001");
|
||||
|
||||
Assertions.assertEquals(2, result.getItems().size());
|
||||
Assertions.assertEquals(1, result.getItems().get(0).getStatSummaries().size());
|
||||
Assertions.assertEquals(1, result.getItems().get(1).getStatSummaries().size());
|
||||
verify(statSummaryService, times(1)).lambdaQuery();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPageItemDetailWhenPageArgumentsPresent() {
|
||||
SteadyChecksquareItemService itemService = mock(SteadyChecksquareItemService.class);
|
||||
SteadyChecksquareDetailService detailService = mock(SteadyChecksquareDetailService.class);
|
||||
SteadyChecksquareItemPO item = new SteadyChecksquareItemPO();
|
||||
item.setId("item-001");
|
||||
item.setState(1);
|
||||
SteadyChecksquareDetailPO detail = new SteadyChecksquareDetailPO();
|
||||
detail.setItemId("item-001");
|
||||
detail.setDetailType("VALUE_ORDER");
|
||||
detail.setPointTime(LocalDateTime.of(2026, 5, 1, 0, 0));
|
||||
detail.setPhase("A");
|
||||
Page<SteadyChecksquareDetailPO> page = new Page<SteadyChecksquareDetailPO>(2, 1);
|
||||
page.setTotal(1);
|
||||
page.setRecords(Collections.singletonList(detail));
|
||||
when(itemService.getById("item-001")).thenReturn(item);
|
||||
when(detailService.page(any(Page.class), any())).thenReturn(page);
|
||||
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
|
||||
mock(SteadyChecksquareInfluxQueryComponent.class), new SteadyChecksquareCalculator(),
|
||||
mock(SteadyChecksquareValueOrderRuleComponent.class), mock(SteadyChecksquareHarmonicParityRuleComponent.class),
|
||||
new AddDataTimeSlotCalculator(), mock(AddLedgerService.class), mock(SteadyChecksquareTaskService.class),
|
||||
itemService, mock(SteadyChecksquareStatSummaryService.class),
|
||||
detailService, new ObjectMapper());
|
||||
|
||||
SteadyChecksquareItemDetailVO result = service.itemDetail("item-001", "VALUE_ORDER", null, 2, 1);
|
||||
|
||||
Assertions.assertEquals(Integer.valueOf(2), result.getPageNum());
|
||||
Assertions.assertEquals(Integer.valueOf(1), result.getPageSize());
|
||||
Assertions.assertEquals(Long.valueOf(1L), result.getTotal());
|
||||
Assertions.assertEquals(1, result.getValueOrderDetails().size());
|
||||
verify(detailService).page(any(Page.class), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSaveChecksquareResultsInBatch() {
|
||||
SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class);
|
||||
SteadyChecksquareItemService itemService = mock(SteadyChecksquareItemService.class);
|
||||
SteadyChecksquareStatSummaryService statSummaryService = mock(SteadyChecksquareStatSummaryService.class);
|
||||
SteadyChecksquareDetailService detailService = mock(SteadyChecksquareDetailService.class);
|
||||
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
|
||||
mock(SteadyChecksquareInfluxQueryComponent.class), new SteadyChecksquareCalculator(),
|
||||
mock(SteadyChecksquareValueOrderRuleComponent.class), mock(SteadyChecksquareHarmonicParityRuleComponent.class),
|
||||
new AddDataTimeSlotCalculator(), mock(AddLedgerService.class), taskService,
|
||||
itemService, statSummaryService, detailService, new ObjectMapper());
|
||||
SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam();
|
||||
param.setIndicatorCodes(Collections.singletonList("V_RMS"));
|
||||
SteadyChecksquareQueryVO result = new SteadyChecksquareQueryVO();
|
||||
result.setLineId("line-001");
|
||||
result.setLineName("进线一");
|
||||
result.setTimeStart("2026-05-01 00:00:00");
|
||||
result.setTimeEnd("2026-05-01 00:01:00");
|
||||
result.setIntervalMinutes(1);
|
||||
SteadyChecksquareItemVO item = new SteadyChecksquareItemVO();
|
||||
item.setItemKey("line-001|V_RMS");
|
||||
item.setIndicatorCode("V_RMS");
|
||||
item.setIndicatorName("相电压有效值");
|
||||
item.setIntervalMinutes(1);
|
||||
item.setHasData(true);
|
||||
item.setExpectedPointCount(2);
|
||||
item.setActualPointCount(2);
|
||||
item.setMissingPointCount(0);
|
||||
item.setMissingRate(BigDecimal.ZERO.setScale(6));
|
||||
item.setMissingRateText("0.00%");
|
||||
item.setMaxContinuousMissingMinutes(0);
|
||||
item.setAbnormal(false);
|
||||
item.setAbnormalPointCount(0);
|
||||
item.setHarmonicParityAbnormal(false);
|
||||
item.setHarmonicParityAbnormalPointCount(0);
|
||||
SteadyChecksquareStatSummaryVO summary = new SteadyChecksquareStatSummaryVO();
|
||||
summary.setStatType("AVG");
|
||||
summary.setSupported(true);
|
||||
summary.setHasData(true);
|
||||
summary.setExpectedPointCount(2);
|
||||
summary.setActualPointCount(2);
|
||||
summary.setMissingPointCount(0);
|
||||
summary.setMissingRate(BigDecimal.ZERO.setScale(6));
|
||||
summary.setMissingRateText("0.00%");
|
||||
summary.setMaxContinuousMissingMinutes(0);
|
||||
item.getStatSummaries().add(summary);
|
||||
result.getItems().add(item);
|
||||
|
||||
saveResult(service, param, result);
|
||||
|
||||
verify(taskService).save(any());
|
||||
verify(itemService).saveBatch(any());
|
||||
verify(statSummaryService).saveBatch(any());
|
||||
}
|
||||
|
||||
private void assertItemInterval(SteadyChecksquareItemVO item, String indicatorCode, int intervalMinutes, int expectedPointCount) {
|
||||
@@ -127,4 +497,66 @@ class SteadyChecksquareServiceImplTest {
|
||||
Assertions.assertEquals(Integer.valueOf(intervalMinutes), item.getIntervalMinutes());
|
||||
Assertions.assertEquals(Integer.valueOf(expectedPointCount), item.getExpectedPointCount());
|
||||
}
|
||||
|
||||
private SteadyChecksquareQueryVO calculate(SteadyChecksquareServiceImpl service, SteadyChecksquareQueryParam param) {
|
||||
try {
|
||||
Method method = SteadyChecksquareServiceImpl.class.getDeclaredMethod("calculate", SteadyChecksquareQueryParam.class);
|
||||
method.setAccessible(true);
|
||||
return (SteadyChecksquareQueryVO) method.invoke(service, param);
|
||||
} catch (Exception exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveResult(SteadyChecksquareServiceImpl service, SteadyChecksquareQueryParam param, SteadyChecksquareQueryVO result) {
|
||||
try {
|
||||
Method method = SteadyChecksquareServiceImpl.class.getDeclaredMethod("saveResult",
|
||||
SteadyChecksquareQueryParam.class, SteadyChecksquareQueryVO.class);
|
||||
method.setAccessible(true);
|
||||
method.invoke(service, param, result);
|
||||
} catch (Exception exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private SteadyChecksquareItemPO buildItemPO(String itemId, String indicatorCode) {
|
||||
SteadyChecksquareItemPO item = new SteadyChecksquareItemPO();
|
||||
item.setId(itemId);
|
||||
item.setIndicatorCode(indicatorCode);
|
||||
item.setIndicatorName(indicatorCode);
|
||||
item.setState(1);
|
||||
item.setHasData(1);
|
||||
item.setExpectedPointCount(1);
|
||||
item.setActualPointCount(1);
|
||||
item.setMissingPointCount(0);
|
||||
item.setMissingRate(BigDecimal.ZERO.setScale(6));
|
||||
item.setMaxContinuousMissingMinutes(0);
|
||||
item.setAbnormal(0);
|
||||
item.setAbnormalPointCount(0);
|
||||
item.setHarmonicParityAbnormal(0);
|
||||
item.setHarmonicParityAbnormalPointCount(0);
|
||||
return item;
|
||||
}
|
||||
|
||||
private SteadyChecksquareStatSummaryPO buildSummaryPO(String itemId, String statType) {
|
||||
SteadyChecksquareStatSummaryPO summary = new SteadyChecksquareStatSummaryPO();
|
||||
summary.setItemId(itemId);
|
||||
summary.setStatType(statType);
|
||||
summary.setSupported(1);
|
||||
summary.setHasData(1);
|
||||
summary.setExpectedPointCount(1);
|
||||
summary.setActualPointCount(1);
|
||||
summary.setMissingPointCount(0);
|
||||
summary.setMissingRate(BigDecimal.ZERO.setScale(6));
|
||||
summary.setMaxContinuousMissingMinutes(0);
|
||||
return summary;
|
||||
}
|
||||
|
||||
private SteadyChecksquareValueOrderRuleVO emptyRuleResult() {
|
||||
return new SteadyChecksquareValueOrderRuleVO();
|
||||
}
|
||||
|
||||
private SteadyChecksquareHarmonicParityRuleVO emptyHarmonicParityRuleResult() {
|
||||
return new SteadyChecksquareHarmonicParityRuleVO();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 模块定位
|
||||
|
||||
`dbms` 是 `system-ops` 下的数据库运维模块,当前面向 Oracle 数据库提供连接配置、连接测试、表列表查询、备份、恢复、任务状态查询和删除接口。
|
||||
`dbms` 是 `system-ops` 下的数据库运维模块,当前支持 Oracle、MySQL 两类数据库运维能力,其中 Oracle 支持 `DATA_PUMP`、`JDBC_EXPORT`,MySQL 当前支持 `JDBC_EXPORT`。
|
||||
|
||||
## 当前接口
|
||||
|
||||
@@ -53,6 +53,7 @@ dbms:
|
||||
backup:
|
||||
storage-path: D:/dbms-backup
|
||||
default-max-file-size-mb: 512
|
||||
mysql-fetch-size: 1000
|
||||
tools:
|
||||
expdp-path:
|
||||
impdp-path:
|
||||
@@ -62,20 +63,31 @@ dbms:
|
||||
|
||||
- `backup.storage-path`
|
||||
- `JDBC_EXPORT` 生成的 CSV 和元数据 JSON 的受管根目录。
|
||||
- `backup.default-max-file-size-mb`
|
||||
- MySQL `JDBC_EXPORT` 默认分片大小,前端可通过 `maxFileSizeMb` 覆盖,默认 512MB。
|
||||
- `backup.mysql-fetch-size`
|
||||
- MySQL `JDBC_EXPORT` 流式读取批量大小,默认 1000。
|
||||
- `tools.expdp-path`、`tools.impdp-path`
|
||||
- Oracle Data Pump 工具路径;为空时尝试走系统 `PATH`。
|
||||
|
||||
## 当前行为
|
||||
|
||||
- 一期仅支持 `ORACLE`。
|
||||
- 连接密码支持两种运行方式:
|
||||
- 前端每次传 `temporaryPassword`。
|
||||
- 连接已保存密文,且公共 `Sm4Utils` 提供 `decryptData_ECB` 时由后端自动解密复用。
|
||||
- 新增连接前的测试接口允许只传 `temporaryPassword`,不强制把密码写进 `connection.password`。
|
||||
- 当前能力矩阵如下:
|
||||
|
||||
| 数据库类型 | 连接测试 | 表列表 | JDBC_EXPORT | DATA_PUMP |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| ORACLE | 支持 | 支持 | 支持 | 支持 |
|
||||
| MYSQL | 支持 | 支持 | 支持 | 不支持 |
|
||||
- 备份和恢复只允许基于已保存且连接可用的连接配置发起。
|
||||
- 新增连接前的测试接口仍可传 `temporaryPassword` 做临时连通性测试。
|
||||
- 备份任务异步执行,只有实际文件生成成功后才会写入 `dbms_backup_file` 记录。
|
||||
- `JDBC_EXPORT` 当前会生成两类文件:
|
||||
- 主数据文件:`*.csv`
|
||||
- 元数据文件:`*_metadata_yyyyMMdd_<taskNo>.json`
|
||||
- MySQL `JDBC_EXPORT` 会按任务号创建独立备份目录,每张表独立 CSV,默认按 512MB 分片:
|
||||
- 数据分片文件:`<table>_part001_yyyyMMdd_<taskNo>.csv`
|
||||
- 元数据文件:`mysql_jdbc_export_metadata_yyyyMMdd_<taskNo>.json`
|
||||
- 备份任务支持停止和重新开始:
|
||||
- `POST /database/backups/tasks/stop`
|
||||
- `POST /database/backups/tasks/restart`
|
||||
- `JDBC_EXPORT` 恢复依赖元数据文件,不再允许缺少元数据直接发起恢复。
|
||||
- 删除备份文件时,会校验目标路径必须位于受管备份目录下,避免误删非备份文件。
|
||||
|
||||
@@ -83,7 +95,7 @@ dbms:
|
||||
|
||||
- `DATA_PUMP` 仍依赖部署机器可执行 `expdp`、`impdp`,并且 Oracle 侧已准备好 `directory` 对象和权限。
|
||||
- 当前代码要求 `DATA_PUMP` 连接配置里补齐可管理的 `directoryPath`,否则虽然 Oracle 端可能已导出成功,后端无法安全管理文件记录与删除。
|
||||
- `JDBC_EXPORT` 恢复当前仅覆盖表数据,不承诺恢复索引、约束、触发器、序列、存储过程、权限等 Oracle 对象。
|
||||
- `JDBC_EXPORT` 恢复当前仅覆盖表数据,不承诺恢复索引、约束、触发器、序列、存储过程、权限等数据库对象。
|
||||
- `TIME_RANGE` 模式当前只在 `JDBC_EXPORT` 场景真正参与查询过滤;`DATA_PUMP` 尚未接入 Oracle `QUERY` 参数。
|
||||
- `SIZE_SPLIT` 参数目前已做入参校验,但尚未实现真正的导出分片。
|
||||
- MySQL `JDBC_EXPORT` 已实现按大小分片;Oracle `JDBC_EXPORT` 仍沿用原单文件导出路径。
|
||||
- 本轮仅完成代码路径和文档收口,未执行 `mvn` 编译、测试或真实库联调。
|
||||
|
||||
@@ -35,5 +35,10 @@
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-tx</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle</groupId>
|
||||
<artifactId>ojdbc6</artifactId>
|
||||
<version>11.2.0.3</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package com.njcn.gather.systemops.database.component;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.common.utils.sm.Sm4Utils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 数据库连接密码处理组件。
|
||||
*/
|
||||
@@ -16,29 +13,16 @@ public class DatabasePasswordComponent {
|
||||
if (StrUtil.isBlank(plainText)) {
|
||||
return null;
|
||||
}
|
||||
return new Sm4Utils(Sm4Utils.globalSecretKey).encryptData_ECB(plainText);
|
||||
return plainText;
|
||||
}
|
||||
|
||||
/**
|
||||
* 优先使用本次请求传入的临时密码;如果公共 SM4 工具存在解密能力,则复用已保存密文。
|
||||
* 优先使用本次请求传入的临时密码;否则复用已保存的数据库密码。
|
||||
*/
|
||||
public String resolveRuntimePassword(String passwordCipher, String temporaryPassword) {
|
||||
if (StrUtil.isNotBlank(temporaryPassword)) {
|
||||
return temporaryPassword;
|
||||
}
|
||||
if (StrUtil.isBlank(passwordCipher)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Sm4Utils sm4Utils = new Sm4Utils(Sm4Utils.globalSecretKey);
|
||||
Method decryptMethod = Sm4Utils.class.getMethod("decryptData_ECB", String.class);
|
||||
Object plainText = decryptMethod.invoke(sm4Utils, passwordCipher);
|
||||
if (plainText instanceof String && StrUtil.isNotBlank((String) plainText)) {
|
||||
return (String) plainText;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// 兼容公共工具不同版本,未找到解密方法时继续走统一失败提示。
|
||||
}
|
||||
throw new IllegalArgumentException("当前环境未确认密码解密方法,请传入临时密码执行本次操作");
|
||||
return StrUtil.isBlank(passwordCipher) ? null : passwordCipher;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,541 @@
|
||||
package com.njcn.gather.systemops.database.component;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.njcn.gather.systemops.database.constant.DatabaseOpsConst;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseBackupParam;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseFileNameUtil;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.Statement;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
/**
|
||||
* JDBC 表数据导出与恢复组件。
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class JdbcExportComponent {
|
||||
|
||||
private static final Pattern IDENTIFIER_PATTERN = Pattern.compile("^[A-Za-z][A-Za-z0-9_#$]*$");
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public void exportCsv(Connection jdbcConnection, String ownerName, DatabaseBackupParam.CreateParam param,
|
||||
Path dataFilePath, Path metadataFilePath) throws Exception {
|
||||
Files.createDirectories(dataFilePath.getParent());
|
||||
if (metadataFilePath.getParent() != null) {
|
||||
Files.createDirectories(metadataFilePath.getParent());
|
||||
}
|
||||
List<TableMetadata> metadataList = new ArrayList<>();
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(dataFilePath, StandardCharsets.UTF_8)) {
|
||||
for (String tableName : param.getTargetNames()) {
|
||||
metadataList.add(exportTable(jdbcConnection, ownerName, tableName, param, writer));
|
||||
}
|
||||
}
|
||||
try (BufferedWriter metadataWriter = Files.newBufferedWriter(metadataFilePath, StandardCharsets.UTF_8)) {
|
||||
objectMapper.writeValue(metadataWriter, metadataList);
|
||||
}
|
||||
}
|
||||
|
||||
public void importCsv(Connection jdbcConnection, Path dataFilePath, Path metadataFilePath, String dbType,
|
||||
String restoreMode, String targetOwnerName) throws Exception {
|
||||
String metadataText = new String(Files.readAllBytes(metadataFilePath), StandardCharsets.UTF_8);
|
||||
if (metadataText.trim().startsWith("{")) {
|
||||
importCsvV2(jdbcConnection, metadataFilePath, dbType, restoreMode, targetOwnerName);
|
||||
return;
|
||||
}
|
||||
List<TableMetadata> metadataList = Arrays.asList(objectMapper.readValue(metadataFilePath.toFile(), TableMetadata[].class));
|
||||
Map<String, TableMetadata> metadataMap = new LinkedHashMap<>();
|
||||
for (TableMetadata metadata : metadataList) {
|
||||
metadataMap.put(metadata.getFullTableName(), metadata);
|
||||
}
|
||||
jdbcConnection.setAutoCommit(false);
|
||||
try (BufferedReader reader = Files.newBufferedReader(dataFilePath, StandardCharsets.UTF_8)) {
|
||||
try {
|
||||
String line;
|
||||
TableMetadata currentMetadata = null;
|
||||
List<String> currentColumns = null;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.startsWith("-- TABLE ")) {
|
||||
currentMetadata = metadataMap.get(line.substring("-- TABLE ".length()).trim());
|
||||
if (currentMetadata == null) {
|
||||
throw new IllegalArgumentException("未找到表元数据:" + line);
|
||||
}
|
||||
currentColumns = null;
|
||||
prepareTargetTable(jdbcConnection, currentMetadata, dbType, restoreMode, targetOwnerName);
|
||||
continue;
|
||||
}
|
||||
if (currentMetadata == null) {
|
||||
continue;
|
||||
}
|
||||
if (currentColumns == null) {
|
||||
currentColumns = parseCsvLine(line);
|
||||
continue;
|
||||
}
|
||||
List<String> values = parseCsvLine(line);
|
||||
insertRow(jdbcConnection, currentMetadata, currentColumns, values, dbType, restoreMode, targetOwnerName);
|
||||
}
|
||||
jdbcConnection.commit();
|
||||
} catch (Exception exception) {
|
||||
jdbcConnection.rollback();
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ExportManifest exportMysqlCsvV2(Connection jdbcConnection, String databaseName, String taskNo,
|
||||
DatabaseBackupParam.CreateParam param, Path backupDirectory,
|
||||
Path metadataFilePath, int fetchSize, long maxPartBytes,
|
||||
BooleanSupplier cancelled) throws Exception {
|
||||
Files.createDirectories(backupDirectory);
|
||||
ExportManifest manifest = new ExportManifest();
|
||||
manifest.setVersion(2);
|
||||
manifest.setDbType("MYSQL");
|
||||
manifest.setBackupStrategy("JDBC_EXPORT");
|
||||
manifest.setTaskNo(taskNo);
|
||||
manifest.setDatabaseName(databaseName);
|
||||
List<TableExportMetadata> tableMetadataList = new ArrayList<>();
|
||||
manifest.setTables(tableMetadataList);
|
||||
for (String tableName : param.getTargetNames()) {
|
||||
checkCancelled(cancelled, backupDirectory);
|
||||
tableMetadataList.add(exportMysqlTableV2(jdbcConnection, tableName, param, backupDirectory, taskNo,
|
||||
fetchSize, maxPartBytes, cancelled));
|
||||
}
|
||||
try (BufferedWriter metadataWriter = Files.newBufferedWriter(metadataFilePath, StandardCharsets.UTF_8)) {
|
||||
objectMapper.writeValue(metadataWriter, manifest);
|
||||
}
|
||||
return manifest;
|
||||
}
|
||||
|
||||
private TableMetadata exportTable(Connection connection, String ownerName, String tableName,
|
||||
DatabaseBackupParam.CreateParam param, BufferedWriter writer) throws Exception {
|
||||
String normalizedOwner = normalizeOwner(ownerName);
|
||||
String normalizedTable = normalizeMysqlIdentifier(tableName);
|
||||
String fullTableName = buildFullTableName(normalizedOwner, normalizedTable);
|
||||
String querySql = buildQuerySql(fullTableName, param);
|
||||
TableMetadata metadata = new TableMetadata();
|
||||
metadata.setOwnerName(normalizedOwner);
|
||||
metadata.setTableName(normalizedTable);
|
||||
metadata.setFullTableName(fullTableName);
|
||||
metadata.setTimeColumn(StrUtil.isBlank(param.getTimeColumn()) ? null : normalizeMysqlIdentifier(param.getTimeColumn()));
|
||||
metadata.setStartTime(param.getStartTime() == null ? null : param.getStartTime().format(DATE_TIME_FORMATTER));
|
||||
metadata.setEndTime(param.getEndTime() == null ? null : param.getEndTime().format(DATE_TIME_FORMATTER));
|
||||
writer.write("-- TABLE " + fullTableName);
|
||||
writer.newLine();
|
||||
try (PreparedStatement statement = connection.prepareStatement(querySql)) {
|
||||
fillQueryParams(statement, param);
|
||||
try (ResultSet resultSet = statement.executeQuery()) {
|
||||
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
|
||||
int columnCount = resultSetMetaData.getColumnCount();
|
||||
List<String> columnNames = new ArrayList<>();
|
||||
List<String> columnTypes = new ArrayList<>();
|
||||
for (int i = 1; i <= columnCount; i++) {
|
||||
String columnName = resultSetMetaData.getColumnName(i);
|
||||
columnNames.add(normalizeMysqlIdentifier(columnName));
|
||||
columnTypes.add(resultSetMetaData.getColumnTypeName(i));
|
||||
if (i > 1) {
|
||||
writer.write(",");
|
||||
}
|
||||
writer.write(escape(columnName));
|
||||
}
|
||||
writer.newLine();
|
||||
long rowCount = 0L;
|
||||
while (resultSet.next()) {
|
||||
for (int i = 1; i <= columnCount; i++) {
|
||||
if (i > 1) {
|
||||
writer.write(",");
|
||||
}
|
||||
writer.write(escape(resultSet.getString(i)));
|
||||
}
|
||||
writer.newLine();
|
||||
rowCount++;
|
||||
}
|
||||
metadata.setColumnNames(columnNames);
|
||||
metadata.setColumnTypes(columnTypes);
|
||||
metadata.setRowCount(rowCount);
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TableExportMetadata exportMysqlTableV2(Connection connection, String tableName,
|
||||
DatabaseBackupParam.CreateParam param, Path backupDirectory,
|
||||
String taskNo, int fetchSize, long maxPartBytes,
|
||||
BooleanSupplier cancelled) throws Exception {
|
||||
String normalizedTable = normalizeIdentifier(tableName);
|
||||
String querySql = buildQuerySql(normalizedTable, param);
|
||||
TableExportMetadata metadata = new TableExportMetadata();
|
||||
metadata.setTableName(normalizedTable);
|
||||
metadata.setFullTableName(normalizedTable);
|
||||
metadata.setTimeColumn(StrUtil.isBlank(param.getTimeColumn()) ? null : normalizeIdentifier(param.getTimeColumn()));
|
||||
metadata.setStartTime(param.getStartTime() == null ? null : param.getStartTime().format(DATE_TIME_FORMATTER));
|
||||
metadata.setEndTime(param.getEndTime() == null ? null : param.getEndTime().format(DATE_TIME_FORMATTER));
|
||||
metadata.setColumns(new ArrayList<>());
|
||||
metadata.setParts(new ArrayList<>());
|
||||
try (PreparedStatement statement = connection.prepareStatement(querySql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
|
||||
statement.setFetchSize(fetchSize);
|
||||
fillQueryParams(statement, param);
|
||||
try (ResultSet resultSet = statement.executeQuery()) {
|
||||
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
|
||||
int columnCount = resultSetMetaData.getColumnCount();
|
||||
List<String> columnNames = new ArrayList<>();
|
||||
for (int i = 1; i <= columnCount; i++) {
|
||||
String columnName = resultSetMetaData.getColumnName(i);
|
||||
columnNames.add(columnName);
|
||||
ColumnMetadata columnMetadata = new ColumnMetadata();
|
||||
columnMetadata.setName(columnName);
|
||||
columnMetadata.setType(resultSetMetaData.getColumnTypeName(i));
|
||||
metadata.getColumns().add(columnMetadata);
|
||||
}
|
||||
PartWriter partWriter = openPartWriter(backupDirectory, normalizedTable, taskNo,
|
||||
metadata.getParts().size() + 1, columnNames);
|
||||
metadata.getParts().add(partWriter.getPart());
|
||||
long totalRows = 0L;
|
||||
try {
|
||||
while (resultSet.next()) {
|
||||
checkCancelled(cancelled, backupDirectory);
|
||||
if (partWriter.shouldRotate(maxPartBytes)) {
|
||||
partWriter.close();
|
||||
partWriter = openPartWriter(backupDirectory, normalizedTable, taskNo,
|
||||
metadata.getParts().size() + 1, columnNames);
|
||||
metadata.getParts().add(partWriter.getPart());
|
||||
}
|
||||
partWriter.writeRow(resultSet, columnCount);
|
||||
totalRows++;
|
||||
}
|
||||
} finally {
|
||||
partWriter.close();
|
||||
}
|
||||
metadata.setRowCount(totalRows);
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String buildQuerySql(String fullTableName, DatabaseBackupParam.CreateParam param) {
|
||||
StringBuilder sql = new StringBuilder("SELECT * FROM ").append(fullTableName);
|
||||
if (param.getStartTime() != null && param.getEndTime() != null && StrUtil.isNotBlank(param.getTimeColumn())) {
|
||||
sql.append(" WHERE ").append(normalizeIdentifier(param.getTimeColumn())).append(" BETWEEN ? AND ?");
|
||||
}
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
private void fillQueryParams(PreparedStatement statement, DatabaseBackupParam.CreateParam param) throws Exception {
|
||||
if (param.getStartTime() != null && param.getEndTime() != null && StrUtil.isNotBlank(param.getTimeColumn())) {
|
||||
statement.setString(1, param.getStartTime().format(DATE_TIME_FORMATTER));
|
||||
statement.setString(2, param.getEndTime().format(DATE_TIME_FORMATTER));
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareTargetTable(Connection connection, TableMetadata metadata, String dbType, String restoreMode,
|
||||
String targetOwnerName) throws Exception {
|
||||
if (!"TRUNCATE".equalsIgnoreCase(restoreMode)
|
||||
&& !("REPLACE".equalsIgnoreCase(restoreMode) && !isMysql(dbType))) {
|
||||
return;
|
||||
}
|
||||
String fullTargetName = buildTargetTableName(metadata, targetOwnerName);
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
statement.execute("TRUNCATE TABLE " + fullTargetName);
|
||||
}
|
||||
}
|
||||
|
||||
private void insertRow(Connection connection, TableMetadata metadata, List<String> columns,
|
||||
List<String> values, String dbType, String restoreMode, String targetOwnerName) throws Exception {
|
||||
String fullTargetName = buildTargetTableName(metadata, targetOwnerName);
|
||||
StringBuilder placeholders = new StringBuilder();
|
||||
for (int i = 0; i < columns.size(); i++) {
|
||||
if (i > 0) {
|
||||
placeholders.append(",");
|
||||
}
|
||||
placeholders.append("?");
|
||||
}
|
||||
String sql = buildInsertSql(dbType, restoreMode, fullTargetName, columns, placeholders.toString());
|
||||
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
for (int i = 0; i < columns.size(); i++) {
|
||||
statement.setString(i + 1, i < values.size() ? values.get(i) : null);
|
||||
}
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private String buildInsertSql(String dbType, String restoreMode, String fullTargetName, List<String> columns,
|
||||
String placeholders) {
|
||||
String command = "INSERT INTO";
|
||||
if (isMysql(dbType) && "SKIP".equalsIgnoreCase(restoreMode)) {
|
||||
// MySQL 跳过重复主键行,避免普通恢复因历史数据重复而整体失败。
|
||||
command = "INSERT IGNORE INTO";
|
||||
} else if (isMysql(dbType) && "REPLACE".equalsIgnoreCase(restoreMode)) {
|
||||
command = "REPLACE INTO";
|
||||
}
|
||||
return command + " " + fullTargetName + " (" + String.join(",", columns) + ") VALUES (" + placeholders + ")";
|
||||
}
|
||||
|
||||
private void importCsvV2(Connection jdbcConnection, Path metadataFilePath, String dbType, String restoreMode,
|
||||
String targetOwnerName) throws Exception {
|
||||
ExportManifest manifest = objectMapper.readValue(metadataFilePath.toFile(), ExportManifest.class);
|
||||
jdbcConnection.setAutoCommit(false);
|
||||
try {
|
||||
for (TableExportMetadata tableMetadata : manifest.getTables()) {
|
||||
prepareTargetTable(jdbcConnection, toLegacyMetadata(tableMetadata), dbType, restoreMode, targetOwnerName);
|
||||
for (FilePartMetadata part : tableMetadata.getParts()) {
|
||||
importPart(jdbcConnection, metadataFilePath.getParent(), tableMetadata, part, dbType, restoreMode, targetOwnerName);
|
||||
}
|
||||
}
|
||||
jdbcConnection.commit();
|
||||
} catch (Exception exception) {
|
||||
jdbcConnection.rollback();
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
private void importPart(Connection jdbcConnection, Path backupDirectory, TableExportMetadata tableMetadata,
|
||||
FilePartMetadata part, String dbType, String restoreMode, String targetOwnerName) throws Exception {
|
||||
Path partPath = backupDirectory.resolve(part.getFileName()).normalize();
|
||||
if (!partPath.startsWith(backupDirectory.normalize())) {
|
||||
throw new IllegalArgumentException("备份分片路径不在元数据目录内:" + part.getFileName());
|
||||
}
|
||||
try (BufferedReader reader = Files.newBufferedReader(partPath, StandardCharsets.UTF_8)) {
|
||||
List<String> columns = null;
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.startsWith("-- TABLE ")) {
|
||||
continue;
|
||||
}
|
||||
if (columns == null) {
|
||||
columns = parseCsvLine(line);
|
||||
continue;
|
||||
}
|
||||
List<String> values = parseCsvLine(line);
|
||||
insertRow(jdbcConnection, toLegacyMetadata(tableMetadata), columns, values, dbType, restoreMode, targetOwnerName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMysql(String dbType) {
|
||||
return DatabaseOpsConst.DB_TYPE_MYSQL.equalsIgnoreCase(dbType);
|
||||
}
|
||||
|
||||
private PartWriter openPartWriter(Path backupDirectory, String tableName, String taskNo, int partIndex,
|
||||
List<String> columnNames) throws IOException {
|
||||
String rawName = tableName.toLowerCase(Locale.ROOT) + "_part" + String.format("%03d", partIndex) + ".csv";
|
||||
String fileName = DatabaseFileNameUtil.appendTodayWithTask(rawName, taskNo);
|
||||
Path filePath = backupDirectory.resolve(fileName).normalize();
|
||||
BufferedWriter writer = Files.newBufferedWriter(filePath, StandardCharsets.UTF_8);
|
||||
writer.write("-- TABLE " + tableName);
|
||||
writer.newLine();
|
||||
for (int i = 0; i < columnNames.size(); i++) {
|
||||
if (i > 0) {
|
||||
writer.write(",");
|
||||
}
|
||||
writer.write(escape(columnNames.get(i)));
|
||||
}
|
||||
writer.newLine();
|
||||
FilePartMetadata part = new FilePartMetadata();
|
||||
part.setFileName(fileName);
|
||||
part.setFilePath(filePath.toString());
|
||||
part.setRowCount(0L);
|
||||
part.setFileSize(0L);
|
||||
return new PartWriter(writer, filePath, part);
|
||||
}
|
||||
|
||||
private TableMetadata toLegacyMetadata(TableExportMetadata metadata) {
|
||||
TableMetadata legacy = new TableMetadata();
|
||||
legacy.setOwnerName(null);
|
||||
legacy.setTableName(metadata.getTableName());
|
||||
legacy.setFullTableName(metadata.getFullTableName());
|
||||
legacy.setTimeColumn(metadata.getTimeColumn());
|
||||
legacy.setStartTime(metadata.getStartTime());
|
||||
legacy.setEndTime(metadata.getEndTime());
|
||||
legacy.setRowCount(metadata.getRowCount());
|
||||
List<String> columnNames = new ArrayList<>();
|
||||
List<String> columnTypes = new ArrayList<>();
|
||||
for (ColumnMetadata column : metadata.getColumns()) {
|
||||
columnNames.add(column.getName());
|
||||
columnTypes.add(column.getType());
|
||||
}
|
||||
legacy.setColumnNames(columnNames);
|
||||
legacy.setColumnTypes(columnTypes);
|
||||
return legacy;
|
||||
}
|
||||
|
||||
private void checkCancelled(BooleanSupplier cancelled, Path backupDirectory) {
|
||||
if (cancelled != null && cancelled.getAsBoolean()) {
|
||||
throw new IllegalStateException("备份任务已停止,已生成文件保留在:" + backupDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildTargetTableName(TableMetadata metadata, String targetOwnerName) {
|
||||
String owner = normalizeOwner(StrUtil.blankToDefault(targetOwnerName, metadata.getOwnerName()));
|
||||
return buildFullTableName(owner, metadata.getTableName());
|
||||
}
|
||||
|
||||
private String buildFullTableName(String ownerName, String tableName) {
|
||||
if (StrUtil.isBlank(ownerName)) {
|
||||
return tableName;
|
||||
}
|
||||
return ownerName + "." + tableName;
|
||||
}
|
||||
|
||||
private String normalizeOwner(String ownerName) {
|
||||
if (StrUtil.isBlank(ownerName)) {
|
||||
return null;
|
||||
}
|
||||
return normalizeIdentifier(ownerName);
|
||||
}
|
||||
|
||||
private List<String> parseCsvLine(String line) {
|
||||
List<String> result = new ArrayList<>();
|
||||
StringBuilder current = new StringBuilder();
|
||||
boolean quoted = false;
|
||||
for (int i = 0; i < line.length(); i++) {
|
||||
char currentChar = line.charAt(i);
|
||||
if (currentChar == '"') {
|
||||
if (quoted && i + 1 < line.length() && line.charAt(i + 1) == '"') {
|
||||
current.append('"');
|
||||
i++;
|
||||
} else {
|
||||
quoted = !quoted;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (currentChar == ',' && !quoted) {
|
||||
result.add(current.toString());
|
||||
current.setLength(0);
|
||||
continue;
|
||||
}
|
||||
current.append(currentChar);
|
||||
}
|
||||
result.add(current.toString());
|
||||
return result;
|
||||
}
|
||||
|
||||
private String escape(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
return "\"" + value.replace("\"", "\"\"") + "\"";
|
||||
}
|
||||
|
||||
private String normalizeIdentifier(String value) {
|
||||
if (value == null || !IDENTIFIER_PATTERN.matcher(value).matches()) {
|
||||
throw new IllegalArgumentException("数据库对象名称格式不正确:" + value);
|
||||
}
|
||||
return value.trim().toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private String normalizeMysqlIdentifier(String value) {
|
||||
if (value == null || !IDENTIFIER_PATTERN.matcher(value).matches()) {
|
||||
throw new IllegalArgumentException("数据库对象名称格式不正确:" + value);
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class TableMetadata {
|
||||
private String ownerName;
|
||||
private String tableName;
|
||||
private String fullTableName;
|
||||
private List<String> columnNames;
|
||||
private List<String> columnTypes;
|
||||
private String timeColumn;
|
||||
private String startTime;
|
||||
private String endTime;
|
||||
private Long rowCount;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ExportManifest {
|
||||
private Integer version;
|
||||
private String dbType;
|
||||
private String backupStrategy;
|
||||
private String taskNo;
|
||||
private String databaseName;
|
||||
private List<TableExportMetadata> tables;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class TableExportMetadata {
|
||||
private String tableName;
|
||||
private String fullTableName;
|
||||
private String timeColumn;
|
||||
private String startTime;
|
||||
private String endTime;
|
||||
private List<ColumnMetadata> columns;
|
||||
private Long rowCount;
|
||||
private List<FilePartMetadata> parts;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ColumnMetadata {
|
||||
private String name;
|
||||
private String type;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class FilePartMetadata {
|
||||
private String fileName;
|
||||
private String filePath;
|
||||
private Long rowCount;
|
||||
private Long fileSize;
|
||||
}
|
||||
|
||||
private class PartWriter {
|
||||
private final BufferedWriter writer;
|
||||
private final Path filePath;
|
||||
private final FilePartMetadata part;
|
||||
|
||||
private PartWriter(BufferedWriter writer, Path filePath, FilePartMetadata part) {
|
||||
this.writer = writer;
|
||||
this.filePath = filePath;
|
||||
this.part = part;
|
||||
}
|
||||
|
||||
private FilePartMetadata getPart() {
|
||||
return part;
|
||||
}
|
||||
|
||||
private boolean shouldRotate(long maxPartBytes) throws IOException {
|
||||
writer.flush();
|
||||
return part.getRowCount() > 0 && Files.size(filePath) >= maxPartBytes;
|
||||
}
|
||||
|
||||
private void writeRow(ResultSet resultSet, int columnCount) throws Exception {
|
||||
for (int i = 1; i <= columnCount; i++) {
|
||||
if (i > 1) {
|
||||
writer.write(",");
|
||||
}
|
||||
writer.write(escape(resultSet.getString(i)));
|
||||
}
|
||||
writer.newLine();
|
||||
part.setRowCount(part.getRowCount() + 1);
|
||||
}
|
||||
|
||||
private void close() throws IOException {
|
||||
writer.close();
|
||||
part.setFileSize(Files.exists(filePath) ? Files.size(filePath) : 0L);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.njcn.gather.systemops.database.component;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.gather.systemops.database.constant.DatabaseOpsConst;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTableVO;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTestResultVO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Oracle JDBC 连接与元数据探测组件。
|
||||
*/
|
||||
@Component
|
||||
public class OracleJdbcComponent {
|
||||
|
||||
public DatabaseTestResultVO test(DatabaseConnection connection, String password) {
|
||||
DatabaseTestResultVO result = new DatabaseTestResultVO();
|
||||
try (Connection ignored = openConnection(connection, password)) {
|
||||
result.setSuccess(true);
|
||||
result.setMessage("连接成功");
|
||||
} catch (Exception exception) {
|
||||
result.setSuccess(false);
|
||||
result.setMessage(exception.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<DatabaseTableVO> listTables(DatabaseConnection connection, String password, String schemaName) throws Exception {
|
||||
String owner = StrUtil.blankToDefault(schemaName, connection.getSchemaName());
|
||||
if (StrUtil.isBlank(owner)) {
|
||||
owner = connection.getUsername();
|
||||
}
|
||||
owner = owner.trim().toUpperCase(Locale.ROOT);
|
||||
String sql = "SELECT t.owner, t.table_name, t.num_rows, o.last_ddl_time, c.comments "
|
||||
+ "FROM all_tables t "
|
||||
+ "LEFT JOIN all_tab_comments c "
|
||||
+ "ON t.owner = c.owner AND t.table_name = c.table_name "
|
||||
+ "LEFT JOIN all_objects o "
|
||||
+ "ON t.owner = o.owner AND t.table_name = o.object_name AND o.object_type = 'TABLE' "
|
||||
+ "WHERE t.owner = ? ORDER BY t.table_name";
|
||||
try (Connection jdbcConnection = openConnection(connection, password);
|
||||
PreparedStatement statement = jdbcConnection.prepareStatement(sql)) {
|
||||
statement.setString(1, owner);
|
||||
try (ResultSet resultSet = statement.executeQuery()) {
|
||||
List<DatabaseTableVO> result = new ArrayList<>();
|
||||
while (resultSet.next()) {
|
||||
DatabaseTableVO table = new DatabaseTableVO();
|
||||
table.setOwner(resultSet.getString("owner"));
|
||||
table.setTableName(resultSet.getString("table_name"));
|
||||
table.setEngine(DatabaseOpsConst.DB_TYPE_ORACLE);
|
||||
table.setTableRows(getLongValue(resultSet, "num_rows"));
|
||||
Timestamp updateTime = resultSet.getTimestamp("last_ddl_time");
|
||||
table.setUpdateTime(updateTime == null ? null : updateTime.toLocalDateTime());
|
||||
table.setComments(resultSet.getString("comments"));
|
||||
result.add(table);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Long getLongValue(ResultSet resultSet, String columnName) throws Exception {
|
||||
long value = resultSet.getLong(columnName);
|
||||
return resultSet.wasNull() ? null : value;
|
||||
}
|
||||
|
||||
public Connection openConnection(DatabaseConnection connection, String password) throws Exception {
|
||||
if (StrUtil.isBlank(password)) {
|
||||
throw new IllegalArgumentException("数据库密码不能为空");
|
||||
}
|
||||
return DriverManager.getConnection(buildJdbcUrl(connection), connection.getUsername(), password);
|
||||
}
|
||||
|
||||
public String buildJdbcUrl(DatabaseConnection connection) {
|
||||
if (DatabaseOpsConst.CONNECT_TYPE_SID.equalsIgnoreCase(connection.getConnectType())) {
|
||||
return "jdbc:oracle:thin:@" + connection.getHost() + ":" + connection.getPort() + ":" + connection.getSid();
|
||||
}
|
||||
return "jdbc:oracle:thin:@//" + connection.getHost() + ":" + connection.getPort() + "/" + connection.getServiceName();
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ public class DbmsProperties {
|
||||
public static class Backup {
|
||||
private String storagePath = "D:/dbms-backup";
|
||||
private Integer defaultMaxFileSizeMb = 512;
|
||||
private Integer mysqlFetchSize = 1000;
|
||||
}
|
||||
|
||||
@Data
|
||||
|
||||
@@ -6,6 +6,7 @@ package com.njcn.gather.systemops.database.constant;
|
||||
public final class DatabaseOpsConst {
|
||||
|
||||
public static final String DB_TYPE_ORACLE = "ORACLE";
|
||||
public static final String DB_TYPE_MYSQL = "MYSQL";
|
||||
public static final String CONNECT_TYPE_SERVICE_NAME = "SERVICE_NAME";
|
||||
public static final String CONNECT_TYPE_SID = "SID";
|
||||
public static final String CONFIRM_DELETE = "确认删除";
|
||||
|
||||
@@ -61,6 +61,22 @@ public class DatabaseBackupController extends BaseController {
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseOperationTaskService.getStatus(taskId), methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
|
||||
@ApiOperation("停止备份任务")
|
||||
@PostMapping("/tasks/stop")
|
||||
public HttpResult<Boolean> stop(@RequestBody @Validated DatabaseBackupParam.StopParam param) {
|
||||
String methodDescribe = getMethodDescribe("stop");
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseOperationTaskService.stopBackupTask(param), methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
|
||||
@ApiOperation("重新开始备份任务")
|
||||
@PostMapping("/tasks/restart")
|
||||
public HttpResult<DatabaseTaskCreateVO> restart(@RequestBody @Validated DatabaseBackupParam.RestartParam param) {
|
||||
String methodDescribe = getMethodDescribe("restart");
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, databaseOperationTaskService.restartBackupTask(param), methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@ApiOperation("查询备份文件")
|
||||
@PostMapping("/files/list")
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.systemops.database.pojo.enums;
|
||||
|
||||
/**
|
||||
* 数据库运维操作类型。
|
||||
*/
|
||||
public enum OperationTypeEnum {
|
||||
BACKUP,
|
||||
RESTORE,
|
||||
DELETE
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.njcn.gather.systemops.database.pojo.enums;
|
||||
|
||||
/**
|
||||
* 恢复模式。
|
||||
*/
|
||||
public enum RestoreModeEnum {
|
||||
SKIP,
|
||||
APPEND,
|
||||
TRUNCATE,
|
||||
REPLACE
|
||||
}
|
||||
@@ -64,4 +64,22 @@ public class DatabaseBackupParam {
|
||||
@ApiModelProperty("备份策略")
|
||||
private String backupStrategy;
|
||||
}
|
||||
|
||||
@Data
|
||||
@ApiModel("停止备份任务参数")
|
||||
public static class StopParam {
|
||||
@ApiModelProperty("备份任务 ID")
|
||||
@NotBlank(message = "备份任务 ID 不能为空")
|
||||
private String taskId;
|
||||
}
|
||||
|
||||
@Data
|
||||
@ApiModel("重新开始备份任务参数")
|
||||
public static class RestartParam {
|
||||
@ApiModelProperty("备份任务 ID")
|
||||
@NotBlank(message = "备份任务 ID 不能为空")
|
||||
private String taskId;
|
||||
@ApiModelProperty("临时密码,原连接未保存密码时传入")
|
||||
private String temporaryPassword;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.njcn.gather.systemops.database.pojo.param;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import com.njcn.web.pojo.param.BaseParam;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
@@ -20,7 +21,7 @@ public class DatabaseConnectionParam {
|
||||
@NotBlank(message = "连接名称不能为空")
|
||||
private String connectionName;
|
||||
|
||||
@ApiModelProperty("数据库类型,一期固定 ORACLE")
|
||||
@ApiModelProperty("数据库类型:ORACLE、MYSQL")
|
||||
private String dbType;
|
||||
|
||||
@ApiModelProperty("数据库主机地址")
|
||||
@@ -40,6 +41,9 @@ public class DatabaseConnectionParam {
|
||||
@ApiModelProperty("SID")
|
||||
private String sid;
|
||||
|
||||
@ApiModelProperty("数据库名,MySQL 使用")
|
||||
private String databaseName;
|
||||
|
||||
@ApiModelProperty("Schema")
|
||||
private String schemaName;
|
||||
|
||||
@@ -113,7 +117,14 @@ public class DatabaseConnectionParam {
|
||||
private String connectionId;
|
||||
@ApiModelProperty("临时密码,不保存密码时传入")
|
||||
private String temporaryPassword;
|
||||
@ApiModelProperty("Schema,不传则使用连接默认 Schema")
|
||||
@ApiModelProperty("兼容前端传入的运行时密码;为空时复用数据库 password_cipher")
|
||||
private String password;
|
||||
@JsonAlias("password_cipher")
|
||||
@ApiModelProperty("兼容前端传入的已保存密码")
|
||||
private String passwordCipher;
|
||||
@ApiModelProperty("兼容前端传入的临时连接参数")
|
||||
private DatabaseConnectionParam connection;
|
||||
@ApiModelProperty("Schema 或数据库名,不传则使用连接默认值")
|
||||
private String schemaName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ public class DatabaseConnectionVO {
|
||||
private String connectType;
|
||||
private String serviceName;
|
||||
private String sid;
|
||||
private String databaseName;
|
||||
private String schemaName;
|
||||
private String username;
|
||||
private Integer savePassword;
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.njcn.gather.systemops.database.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 数据库表信息。
|
||||
*/
|
||||
@@ -9,5 +11,11 @@ import lombok.Data;
|
||||
public class DatabaseTableVO {
|
||||
private String owner;
|
||||
private String tableName;
|
||||
private Long autoIncrementValue = 0L;
|
||||
private Long autoIncrement = 0L;
|
||||
private LocalDateTime updateTime;
|
||||
private Long dataLength;
|
||||
private String engine;
|
||||
private Long tableRows;
|
||||
private String comments;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ public interface DatabaseOperationTaskService extends IService<DatabaseOperation
|
||||
|
||||
DatabaseTaskVO getStatus(String taskId);
|
||||
|
||||
boolean stopBackupTask(DatabaseBackupParam.StopParam param);
|
||||
|
||||
DatabaseTaskCreateVO restartBackupTask(DatabaseBackupParam.RestartParam param);
|
||||
|
||||
boolean deleteTask(String taskId, String confirmText);
|
||||
|
||||
boolean existsRunningTask(String connectionId);
|
||||
|
||||
@@ -24,7 +24,9 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Comparator;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 数据库备份文件服务实现。
|
||||
@@ -63,9 +65,9 @@ public class DatabaseBackupFileServiceImpl extends ServiceImpl<DatabaseBackupFil
|
||||
if (file == null) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份文件不存在或已删除");
|
||||
}
|
||||
deletePhysicalFile(file, file.getFilePath());
|
||||
deletePhysicalFile(file, file.getLogFilePath());
|
||||
deletePhysicalFile(file, file.getMetadataFilePath());
|
||||
deletePhysicalPath(file, file.getFilePath());
|
||||
deletePhysicalPath(file, file.getLogFilePath());
|
||||
deletePhysicalPath(file, file.getMetadataFilePath());
|
||||
file.setState(DatabaseOpsConst.STATE_DELETED);
|
||||
file.setUpdateTime(LocalDateTime.now());
|
||||
return this.updateById(file);
|
||||
@@ -73,14 +75,14 @@ public class DatabaseBackupFileServiceImpl extends ServiceImpl<DatabaseBackupFil
|
||||
|
||||
@Override
|
||||
public void validateBackupFileReadable(DatabaseBackupFile backupFile) {
|
||||
validateReadablePath(backupFile, backupFile.getFilePath(), "备份文件", false);
|
||||
validateReadablePath(backupFile, backupFile.getFilePath(), "备份文件", false, true);
|
||||
validateReadablePath(backupFile, backupFile.getMetadataFilePath(), "备份元数据文件",
|
||||
StrUtil.isBlank(backupFile.getMetadataFilePath()));
|
||||
StrUtil.isBlank(backupFile.getMetadataFilePath()), false);
|
||||
if (StrUtil.isBlank(backupFile.getChecksum())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份文件缺少校验值");
|
||||
}
|
||||
Path filePath = resolveManagedPath(backupFile, backupFile.getFilePath());
|
||||
String actualChecksum = DatabaseChecksumUtil.sha256(filePath);
|
||||
Path checksumPath = resolveChecksumPath(backupFile);
|
||||
String actualChecksum = DatabaseChecksumUtil.sha256(checksumPath);
|
||||
if (!backupFile.getChecksum().equalsIgnoreCase(actualChecksum)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份文件校验失败");
|
||||
}
|
||||
@@ -100,20 +102,41 @@ public class DatabaseBackupFileServiceImpl extends ServiceImpl<DatabaseBackupFil
|
||||
return path;
|
||||
}
|
||||
Path primaryFilePath = DatabasePathUtil.normalize(backupFile.getFilePath());
|
||||
if (primaryFilePath != null && primaryFilePath.getParent() != null
|
||||
&& DatabasePathUtil.isUnder(path, primaryFilePath.getParent())) {
|
||||
return path;
|
||||
if (primaryFilePath != null) {
|
||||
Path allowedRoot = Files.isDirectory(primaryFilePath) ? primaryFilePath : primaryFilePath.getParent();
|
||||
if (allowedRoot != null && DatabasePathUtil.isUnder(path, allowedRoot)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "文件路径不在允许的备份目录内");
|
||||
}
|
||||
|
||||
private void deletePhysicalFile(DatabaseBackupFile backupFile, String filePath) {
|
||||
private Path resolveChecksumPath(DatabaseBackupFile backupFile) {
|
||||
Path metadataPath = resolveManagedPath(backupFile, backupFile.getMetadataFilePath());
|
||||
if (metadataPath != null && Files.exists(metadataPath) && !Files.isDirectory(metadataPath)) {
|
||||
return metadataPath;
|
||||
}
|
||||
Path filePath = resolveManagedPath(backupFile, backupFile.getFilePath());
|
||||
if (filePath == null || !Files.exists(filePath) || Files.isDirectory(filePath)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份校验文件不存在");
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
private void deletePhysicalPath(DatabaseBackupFile backupFile, String filePath) {
|
||||
if (StrUtil.isBlank(filePath)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Path path = resolveManagedPath(backupFile, filePath);
|
||||
if (path != null && Files.exists(path) && !Files.isDirectory(path)) {
|
||||
if (path == null || !Files.exists(path)) {
|
||||
return;
|
||||
}
|
||||
if (Files.isDirectory(path)) {
|
||||
try (Stream<Path> paths = Files.walk(path)) {
|
||||
paths.sorted(Comparator.reverseOrder()).forEach(this::deleteSinglePath);
|
||||
}
|
||||
} else {
|
||||
Files.delete(path);
|
||||
}
|
||||
} catch (BusinessException exception) {
|
||||
@@ -123,7 +146,16 @@ public class DatabaseBackupFileServiceImpl extends ServiceImpl<DatabaseBackupFil
|
||||
}
|
||||
}
|
||||
|
||||
private void validateReadablePath(DatabaseBackupFile backupFile, String filePath, String fileType, boolean allowBlank) {
|
||||
private void deleteSinglePath(Path path) {
|
||||
try {
|
||||
Files.deleteIfExists(path);
|
||||
} catch (Exception exception) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "删除物理文件失败:" + exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void validateReadablePath(DatabaseBackupFile backupFile, String filePath, String fileType,
|
||||
boolean allowBlank, boolean allowDirectory) {
|
||||
if (StrUtil.isBlank(filePath)) {
|
||||
if (allowBlank) {
|
||||
return;
|
||||
@@ -131,9 +163,12 @@ public class DatabaseBackupFileServiceImpl extends ServiceImpl<DatabaseBackupFil
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, fileType + "路径不能为空");
|
||||
}
|
||||
Path path = resolveManagedPath(backupFile, filePath);
|
||||
if (path == null || !Files.exists(path) || Files.isDirectory(path)) {
|
||||
if (path == null || !Files.exists(path)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, fileType + "不存在");
|
||||
}
|
||||
if (Files.isDirectory(path) && !allowDirectory) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, fileType + "不能是目录");
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseBackupFileVO toVO(DatabaseBackupFile file) {
|
||||
|
||||
@@ -8,7 +8,6 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.gather.systemops.database.component.DatabasePasswordComponent;
|
||||
import com.njcn.gather.systemops.database.component.OracleJdbcComponent;
|
||||
import com.njcn.gather.systemops.database.constant.DatabaseOpsConst;
|
||||
import com.njcn.gather.systemops.database.mapper.DatabaseConnectionMapper;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseConnectionParam;
|
||||
@@ -18,6 +17,8 @@ import com.njcn.gather.systemops.database.pojo.vo.DatabaseTableVO;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTestResultVO;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseConnectionService;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseOperationTaskService;
|
||||
import com.njcn.gather.systemops.database.support.spi.DatabaseConnectionOperator;
|
||||
import com.njcn.gather.systemops.database.support.spi.DatabaseOperatorRegistry;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseOpsIdUtil;
|
||||
import com.njcn.web.factory.PageFactory;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -38,7 +39,7 @@ import java.util.stream.Collectors;
|
||||
public class DatabaseConnectionServiceImpl extends ServiceImpl<DatabaseConnectionMapper, DatabaseConnection> implements DatabaseConnectionService {
|
||||
|
||||
private final DatabasePasswordComponent databasePasswordComponent;
|
||||
private final OracleJdbcComponent oracleJdbcComponent;
|
||||
private final DatabaseOperatorRegistry databaseOperatorRegistry;
|
||||
private final ObjectProvider<DatabaseOperationTaskService> databaseOperationTaskServiceProvider;
|
||||
|
||||
@Override
|
||||
@@ -61,6 +62,7 @@ public class DatabaseConnectionServiceImpl extends ServiceImpl<DatabaseConnectio
|
||||
public boolean addConnection(DatabaseConnectionParam param) {
|
||||
DatabaseConnection connection = new DatabaseConnection();
|
||||
fillConnection(connection, param, true);
|
||||
checkConnectionNameUnique(connection.getConnectionName());
|
||||
connection.setId(DatabaseOpsIdUtil.uuid());
|
||||
connection.setState(DatabaseOpsConst.STATE_ENABLED);
|
||||
connection.setCreateTime(LocalDateTime.now());
|
||||
@@ -95,12 +97,10 @@ public class DatabaseConnectionServiceImpl extends ServiceImpl<DatabaseConnectio
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public DatabaseTestResultVO testConnection(DatabaseConnectionParam.TestParam param) {
|
||||
DatabaseConnection connection = resolveTestConnection(param);
|
||||
DatabaseTestResultVO result = oracleJdbcComponent.test(connection, resolvePassword(connection, param.getTemporaryPassword()));
|
||||
DatabaseConnectionOperator operator = databaseOperatorRegistry.getConnectionOperator(connection.getDbType());
|
||||
DatabaseTestResultVO result = operator.test(connection, resolvePassword(connection, param.getTemporaryPassword()));
|
||||
if (StrUtil.isNotBlank(connection.getId())) {
|
||||
connection.setLastTestStatus(Boolean.TRUE.equals(result.getSuccess()) ? "SUCCESS" : "FAIL");
|
||||
connection.setLastTestMessage(result.getMessage());
|
||||
connection.setLastTestTime(LocalDateTime.now());
|
||||
this.updateById(connection);
|
||||
updateLastTestResult(connection.getId(), result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -109,7 +109,10 @@ public class DatabaseConnectionServiceImpl extends ServiceImpl<DatabaseConnectio
|
||||
public List<DatabaseTableVO> listTables(DatabaseConnectionParam.TablesParam param) {
|
||||
DatabaseConnection connection = requireEnabled(param.getConnectionId());
|
||||
try {
|
||||
return oracleJdbcComponent.listTables(connection, resolvePassword(connection, param.getTemporaryPassword()), param.getSchemaName());
|
||||
DatabaseConnectionOperator operator = databaseOperatorRegistry.getConnectionOperator(connection.getDbType());
|
||||
String password = resolveTablesPassword(connection, param);
|
||||
return operator.listTables(connection, password,
|
||||
resolveSchemaOrDatabase(param, connection));
|
||||
} catch (Exception exception) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, exception.getMessage());
|
||||
}
|
||||
@@ -141,7 +144,18 @@ public class DatabaseConnectionServiceImpl extends ServiceImpl<DatabaseConnectio
|
||||
|
||||
private DatabaseConnection resolveTestConnection(DatabaseConnectionParam.TestParam param) {
|
||||
if (StrUtil.isNotBlank(param.getConnectionId())) {
|
||||
return requireEnabled(param.getConnectionId());
|
||||
DatabaseConnection savedConnection = requireEnabled(param.getConnectionId());
|
||||
if (param.getConnection() == null) {
|
||||
return savedConnection;
|
||||
}
|
||||
DatabaseConnection connection = new DatabaseConnection();
|
||||
fillConnection(connection, param.getConnection(), true, true);
|
||||
connection.setId(savedConnection.getId());
|
||||
if (StrUtil.isBlank(param.getConnection().getPassword())) {
|
||||
// 已有连接测试编辑后参数时,未传密码则复用库里保存的密码。
|
||||
connection.setPasswordCipher(savedConnection.getPasswordCipher());
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
if (param.getConnection() == null) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "连接测试参数不能为空");
|
||||
@@ -151,20 +165,47 @@ public class DatabaseConnectionServiceImpl extends ServiceImpl<DatabaseConnectio
|
||||
return connection;
|
||||
}
|
||||
|
||||
private void updateLastTestResult(String connectionId, DatabaseTestResultVO result) {
|
||||
this.lambdaUpdate()
|
||||
.set(DatabaseConnection::getLastTestStatus, Boolean.TRUE.equals(result.getSuccess()) ? "SUCCESS" : "FAIL")
|
||||
.set(DatabaseConnection::getLastTestMessage, result.getMessage())
|
||||
.set(DatabaseConnection::getLastTestTime, LocalDateTime.now())
|
||||
.eq(DatabaseConnection::getId, connectionId)
|
||||
.update();
|
||||
}
|
||||
|
||||
private String resolveTablesPassword(DatabaseConnection connection, DatabaseConnectionParam.TablesParam param) {
|
||||
if (StrUtil.isNotBlank(param.getTemporaryPassword())) {
|
||||
return param.getTemporaryPassword();
|
||||
}
|
||||
if (StrUtil.isNotBlank(param.getPassword())) {
|
||||
return param.getPassword();
|
||||
}
|
||||
if (param.getConnection() != null && StrUtil.isNotBlank(param.getConnection().getPassword())) {
|
||||
return param.getConnection().getPassword();
|
||||
}
|
||||
if (StrUtil.isNotBlank(param.getPasswordCipher())) {
|
||||
return databasePasswordComponent.resolveRuntimePassword(param.getPasswordCipher(), null);
|
||||
}
|
||||
return resolvePassword(connection, null);
|
||||
}
|
||||
|
||||
private void fillConnection(DatabaseConnection connection, DatabaseConnectionParam param, boolean create) {
|
||||
fillConnection(connection, param, create, false);
|
||||
}
|
||||
|
||||
private void fillConnection(DatabaseConnection connection, DatabaseConnectionParam param, boolean create,
|
||||
boolean allowTemporaryPasswordOnly) {
|
||||
validateConnectionParam(param);
|
||||
String dbType = resolveDbType(param.getDbType());
|
||||
validateConnectionParam(param, dbType);
|
||||
connection.setConnectionName(param.getConnectionName().trim());
|
||||
connection.setDbType(DatabaseOpsConst.DB_TYPE_ORACLE);
|
||||
connection.setDbType(dbType);
|
||||
connection.setHost(param.getHost().trim());
|
||||
connection.setPort(param.getPort());
|
||||
connection.setConnectType(resolveConnectType(param.getConnectType()));
|
||||
connection.setServiceName(trimToNull(param.getServiceName()));
|
||||
connection.setSid(trimToNull(param.getSid()));
|
||||
connection.setConnectType(DatabaseOpsConst.DB_TYPE_ORACLE.equals(dbType) ? resolveConnectType(param.getConnectType()) : null);
|
||||
connection.setServiceName(DatabaseOpsConst.DB_TYPE_ORACLE.equals(dbType) ? trimToNull(param.getServiceName()) : null);
|
||||
connection.setSid(DatabaseOpsConst.DB_TYPE_ORACLE.equals(dbType) ? trimToNull(param.getSid()) : null);
|
||||
connection.setDatabaseName(DatabaseOpsConst.DB_TYPE_MYSQL.equals(dbType) ? trimToNull(param.getDatabaseName()) : null);
|
||||
connection.setSchemaName(trimToNull(param.getSchemaName()));
|
||||
connection.setUsername(param.getUsername().trim());
|
||||
connection.setSavePassword(param.getSavePassword() == null ? DatabaseOpsConst.SAVE_PASSWORD_YES : param.getSavePassword());
|
||||
@@ -180,19 +221,38 @@ public class DatabaseConnectionServiceImpl extends ServiceImpl<DatabaseConnectio
|
||||
} else if (create && StrUtil.isBlank(param.getPassword()) && !allowTemporaryPasswordOnly) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "保存密码时密码不能为空");
|
||||
}
|
||||
connection.setDirectoryName(trimToNull(param.getDirectoryName()));
|
||||
connection.setDirectoryPath(trimToNull(param.getDirectoryPath()));
|
||||
connection.setDirectoryName(DatabaseOpsConst.DB_TYPE_ORACLE.equals(dbType) ? trimToNull(param.getDirectoryName()) : null);
|
||||
connection.setDirectoryPath(DatabaseOpsConst.DB_TYPE_ORACLE.equals(dbType) ? trimToNull(param.getDirectoryPath()) : null);
|
||||
connection.setExtraConfigJson(trimToNull(param.getExtraConfigJson()));
|
||||
connection.setRemark(param.getRemark());
|
||||
}
|
||||
|
||||
private void validateConnectionParam(DatabaseConnectionParam param) {
|
||||
String connectType = resolveConnectType(param.getConnectType());
|
||||
if (DatabaseOpsConst.CONNECT_TYPE_SERVICE_NAME.equals(connectType) && StrUtil.isBlank(param.getServiceName())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "SERVICE_NAME 连接方式下服务名不能为空");
|
||||
private void validateConnectionParam(DatabaseConnectionParam param, String dbType) {
|
||||
if (DatabaseOpsConst.DB_TYPE_ORACLE.equals(dbType)) {
|
||||
String connectType = resolveConnectType(param.getConnectType());
|
||||
if (DatabaseOpsConst.CONNECT_TYPE_SERVICE_NAME.equals(connectType) && StrUtil.isBlank(param.getServiceName())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "SERVICE_NAME 连接方式下服务名不能为空");
|
||||
}
|
||||
if (DatabaseOpsConst.CONNECT_TYPE_SID.equals(connectType) && StrUtil.isBlank(param.getSid())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "SID 连接方式下 SID 不能为空");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (DatabaseOpsConst.CONNECT_TYPE_SID.equals(connectType) && StrUtil.isBlank(param.getSid())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "SID 连接方式下 SID 不能为空");
|
||||
if (StrUtil.isBlank(param.getDatabaseName())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "MYSQL 数据库名不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增连接时,连接名称在有效记录中必须唯一。
|
||||
*/
|
||||
private void checkConnectionNameUnique(String connectionName) {
|
||||
long count = this.lambdaQuery()
|
||||
.eq(DatabaseConnection::getConnectionName, connectionName)
|
||||
.eq(DatabaseConnection::getState, DatabaseOpsConst.STATE_ENABLED)
|
||||
.count();
|
||||
if (count > 0) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "连接名称已存在");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,6 +260,21 @@ public class DatabaseConnectionServiceImpl extends ServiceImpl<DatabaseConnectio
|
||||
return StrUtil.blankToDefault(connectType, DatabaseOpsConst.CONNECT_TYPE_SERVICE_NAME).trim().toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private String resolveDbType(String dbType) {
|
||||
String resolved = StrUtil.blankToDefault(dbType, DatabaseOpsConst.DB_TYPE_ORACLE).trim().toUpperCase(Locale.ROOT);
|
||||
if (!DatabaseOpsConst.DB_TYPE_ORACLE.equals(resolved) && !DatabaseOpsConst.DB_TYPE_MYSQL.equals(resolved)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "不支持的数据库类型:" + dbType);
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private String resolveSchemaOrDatabase(DatabaseConnectionParam.TablesParam param, DatabaseConnection connection) {
|
||||
if (DatabaseOpsConst.DB_TYPE_MYSQL.equals(connection.getDbType())) {
|
||||
return StrUtil.blankToDefault(param.getSchemaName(), connection.getDatabaseName());
|
||||
}
|
||||
return param.getSchemaName();
|
||||
}
|
||||
|
||||
private String trimToNull(String value) {
|
||||
return StrUtil.isBlank(value) ? null : value.trim();
|
||||
}
|
||||
|
||||
@@ -8,14 +8,10 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.gather.systemops.database.component.DataPumpCommandExecutor;
|
||||
import com.njcn.gather.systemops.database.component.JdbcExportComponent;
|
||||
import com.njcn.gather.systemops.database.config.DbmsProperties;
|
||||
import com.njcn.gather.systemops.database.constant.DatabaseOpsConst;
|
||||
import com.njcn.gather.systemops.database.mapper.DatabaseOperationTaskMapper;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.BackupModeEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.BackupStrategyEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.FileFormatEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.OperationTypeEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.TaskStatusEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseBackupParam;
|
||||
@@ -27,10 +23,9 @@ import com.njcn.gather.systemops.database.pojo.vo.DatabaseTaskVO;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseBackupFileService;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseConnectionService;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseOperationTaskService;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseChecksumUtil;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseFileNameUtil;
|
||||
import com.njcn.gather.systemops.database.support.spi.DatabaseBackupOperator;
|
||||
import com.njcn.gather.systemops.database.support.spi.DatabaseOperatorRegistry;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseOpsIdUtil;
|
||||
import com.njcn.gather.systemops.database.util.DatabasePathUtil;
|
||||
import com.njcn.web.factory.PageFactory;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -39,8 +34,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
@@ -57,9 +50,7 @@ public class DatabaseOperationTaskServiceImpl extends ServiceImpl<DatabaseOperat
|
||||
|
||||
private final DatabaseConnectionService databaseConnectionService;
|
||||
private final DatabaseBackupFileService databaseBackupFileService;
|
||||
private final DataPumpCommandExecutor dataPumpCommandExecutor;
|
||||
private final JdbcExportComponent jdbcExportComponent;
|
||||
private final DbmsProperties dbmsProperties;
|
||||
private final DatabaseOperatorRegistry databaseOperatorRegistry;
|
||||
private final ObjectMapper objectMapper;
|
||||
@Resource(name = "dbmsTaskExecutorService")
|
||||
private ExecutorService dbmsTaskExecutorService;
|
||||
@@ -95,11 +86,41 @@ public class DatabaseOperationTaskServiceImpl extends ServiceImpl<DatabaseOperat
|
||||
|
||||
@Override
|
||||
public DatabaseTaskVO getStatus(String taskId) {
|
||||
DatabaseOperationTask task = this.getById(taskId);
|
||||
if (task == null || !Integer.valueOf(DatabaseOpsConst.STATE_ENABLED).equals(task.getState())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "任务不存在或已删除");
|
||||
return toVO(requireEnabledTask(taskId));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean stopBackupTask(DatabaseBackupParam.StopParam param) {
|
||||
DatabaseOperationTask task = requireEnabledTask(param.getTaskId());
|
||||
if (!OperationTypeEnum.BACKUP.name().equals(task.getOperationType())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "仅支持停止备份任务");
|
||||
}
|
||||
return toVO(task);
|
||||
if (!TaskStatusEnum.WAITING.name().equals(task.getTaskStatus())
|
||||
&& !TaskStatusEnum.RUNNING.name().equals(task.getTaskStatus())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "仅等待中或运行中的任务允许停止");
|
||||
}
|
||||
task.setTaskStatus(TaskStatusEnum.CANCELLED.name());
|
||||
task.setResultMessage("用户请求停止备份任务");
|
||||
task.setFinishedAt(LocalDateTime.now());
|
||||
task.setUpdateTime(LocalDateTime.now());
|
||||
return this.updateById(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public DatabaseTaskCreateVO restartBackupTask(DatabaseBackupParam.RestartParam param) {
|
||||
DatabaseOperationTask sourceTask = requireEnabledTask(param.getTaskId());
|
||||
if (!OperationTypeEnum.BACKUP.name().equals(sourceTask.getOperationType())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "仅支持重新开始备份任务");
|
||||
}
|
||||
if (!TaskStatusEnum.FAIL.name().equals(sourceTask.getTaskStatus())
|
||||
&& !TaskStatusEnum.CANCELLED.name().equals(sourceTask.getTaskStatus())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "仅失败或已取消的任务允许重新开始");
|
||||
}
|
||||
DatabaseBackupParam.CreateParam createParam = readCreateParam(sourceTask.getRequestParamJson());
|
||||
createParam.setTemporaryPassword(param.getTemporaryPassword());
|
||||
return createBackupTask(createParam);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,10 +129,7 @@ public class DatabaseOperationTaskServiceImpl extends ServiceImpl<DatabaseOperat
|
||||
if (!DatabaseOpsConst.CONFIRM_DELETE.equals(confirmText)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "确认文案不正确");
|
||||
}
|
||||
DatabaseOperationTask task = this.getById(taskId);
|
||||
if (task == null || !Integer.valueOf(DatabaseOpsConst.STATE_ENABLED).equals(task.getState())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "任务不存在或已删除");
|
||||
}
|
||||
DatabaseOperationTask task = requireEnabledTask(taskId);
|
||||
if (TaskStatusEnum.RUNNING.name().equals(task.getTaskStatus()) || TaskStatusEnum.WAITING.name().equals(task.getTaskStatus())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "运行中的任务不能删除");
|
||||
}
|
||||
@@ -132,113 +150,32 @@ public class DatabaseOperationTaskServiceImpl extends ServiceImpl<DatabaseOperat
|
||||
private void executeBackupTask(String taskId, DatabaseBackupParam.CreateParam param) {
|
||||
DatabaseOperationTask task = this.getById(taskId);
|
||||
try {
|
||||
if (task == null || TaskStatusEnum.CANCELLED.name().equals(task.getTaskStatus())) {
|
||||
return;
|
||||
}
|
||||
markRunning(task);
|
||||
DatabaseConnection connection = databaseConnectionService.requireEnabled(task.getConnectionId());
|
||||
connection.setSchemaName(task.getSchemaName());
|
||||
String password = databaseConnectionService.resolvePassword(connection, param.getTemporaryPassword());
|
||||
DatabaseBackupFile backupFile;
|
||||
if (BackupStrategyEnum.DATA_PUMP.name().equals(task.getBackupStrategy())) {
|
||||
backupFile = executeDataPumpBackup(task, connection, password, param);
|
||||
} else {
|
||||
backupFile = executeJdbcExportBackup(task, connection, password, param);
|
||||
DatabaseBackupOperator operator = databaseOperatorRegistry.getBackupOperator(connection.getDbType(), task.getBackupStrategy());
|
||||
DatabaseBackupFile backupFile = operator.executeBackup(task, connection, password, param);
|
||||
task = this.getById(taskId);
|
||||
if (task == null || TaskStatusEnum.CANCELLED.name().equals(task.getTaskStatus())) {
|
||||
return;
|
||||
}
|
||||
databaseBackupFileService.save(backupFile);
|
||||
markSuccess(task, "备份任务执行成功");
|
||||
} catch (Exception exception) {
|
||||
log.error("数据库备份任务失败,taskId={}", taskId, exception);
|
||||
task = this.getById(taskId);
|
||||
if (task != null && TaskStatusEnum.CANCELLED.name().equals(task.getTaskStatus())) {
|
||||
markCancelled(task, exception.getMessage());
|
||||
return;
|
||||
}
|
||||
markFail(task, exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseBackupFile executeDataPumpBackup(DatabaseOperationTask task, DatabaseConnection connection, String password,
|
||||
DatabaseBackupParam.CreateParam param) {
|
||||
String directoryName = StrUtil.blankToDefault(param.getDirectoryName(), connection.getDirectoryName());
|
||||
if (StrUtil.isBlank(directoryName)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "DATA_PUMP 备份需要 Oracle Directory 名称");
|
||||
}
|
||||
String baseName = buildBaseFileName(connection, task);
|
||||
String dumpFileName = DatabaseFileNameUtil.appendTodayWithTask(baseName + ".dmp", task.getTaskNo());
|
||||
String logFileName = DatabaseFileNameUtil.appendTodayWithTask(baseName + ".log", task.getTaskNo());
|
||||
DataPumpCommandExecutor.CommandResult commandResult = dataPumpCommandExecutor.expdp(connection, password, directoryName, dumpFileName, logFileName, param.getTargetNames());
|
||||
if (!Boolean.TRUE.equals(commandResult.getSuccess())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "Data Pump 执行失败:" + commandResult.getOutput());
|
||||
}
|
||||
if (StrUtil.isBlank(connection.getDirectoryPath())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "Data Pump 备份需要配置可管理的 directoryPath");
|
||||
}
|
||||
Path dumpPath = buildManagedPath(connection.getDirectoryPath(), dumpFileName);
|
||||
Path logPath = buildManagedPath(connection.getDirectoryPath(), logFileName);
|
||||
return buildBackupFile(task, connection, param, FileFormatEnum.DMP.name(), dumpFileName, dumpPath, logFileName, logPath, null);
|
||||
}
|
||||
|
||||
private DatabaseBackupFile executeJdbcExportBackup(DatabaseOperationTask task, DatabaseConnection connection, String password,
|
||||
DatabaseBackupParam.CreateParam param) throws Exception {
|
||||
String baseName = buildBaseFileName(connection, task);
|
||||
String fileName = DatabaseFileNameUtil.appendTodayWithTask(baseName + ".csv", task.getTaskNo());
|
||||
String metadataFileName = DatabaseFileNameUtil.appendTodayWithTask(baseName + "_metadata.json", task.getTaskNo());
|
||||
Path dataFilePath = buildManagedPath(dbmsProperties.getBackup().getStoragePath(), fileName);
|
||||
Path metadataFilePath = buildManagedPath(dbmsProperties.getBackup().getStoragePath(), metadataFileName);
|
||||
jdbcExportComponent.exportCsv(connection, password, param, dataFilePath, metadataFilePath);
|
||||
return buildBackupFile(task, connection, param, FileFormatEnum.CSV.name(), fileName, dataFilePath, null, null, metadataFilePath);
|
||||
}
|
||||
|
||||
private DatabaseBackupFile buildBackupFile(DatabaseOperationTask task, DatabaseConnection connection, DatabaseBackupParam.CreateParam param,
|
||||
String fileFormat, String fileName, Path filePath, String logFileName, Path logFilePath,
|
||||
Path metadataFilePath) {
|
||||
if (filePath == null || !Files.exists(filePath)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份文件未生成");
|
||||
}
|
||||
DatabaseBackupFile file = new DatabaseBackupFile();
|
||||
file.setId(DatabaseOpsIdUtil.uuid());
|
||||
file.setTaskId(task.getId());
|
||||
file.setConnectionId(connection.getId());
|
||||
file.setDbType(connection.getDbType());
|
||||
file.setBackupStrategy(task.getBackupStrategy());
|
||||
file.setFileFormat(fileFormat);
|
||||
file.setSchemaName(task.getSchemaName());
|
||||
file.setTargetNamesJson(task.getTargetNamesJson());
|
||||
file.setBackupMode(StrUtil.blankToDefault(param.getBackupMode(), BackupModeEnum.FULL_TABLE.name()).toUpperCase(Locale.ROOT));
|
||||
file.setBackupStartTime(param.getStartTime());
|
||||
file.setBackupEndTime(param.getEndTime());
|
||||
file.setTimeColumn(param.getTimeColumn());
|
||||
file.setDirectoryName(StrUtil.blankToDefault(param.getDirectoryName(), connection.getDirectoryName()));
|
||||
file.setDumpFileName(FileFormatEnum.DMP.name().equals(fileFormat) ? fileName : null);
|
||||
file.setLogFileName(logFileName);
|
||||
file.setFileName(fileName);
|
||||
file.setFilePath(filePath.toString());
|
||||
file.setLogFilePath(logFilePath == null ? null : logFilePath.toString());
|
||||
file.setMetadataFilePath(metadataFilePath == null ? null : metadataFilePath.toString());
|
||||
file.setFileSize(readFileSize(filePath));
|
||||
file.setChecksum(DatabaseChecksumUtil.sha256(filePath));
|
||||
file.setState(DatabaseOpsConst.STATE_ENABLED);
|
||||
file.setCreateTime(LocalDateTime.now());
|
||||
file.setUpdateTime(LocalDateTime.now());
|
||||
return file;
|
||||
}
|
||||
|
||||
private Long readFileSize(Path filePath) {
|
||||
try {
|
||||
if (filePath != null && Files.exists(filePath) && !Files.isDirectory(filePath)) {
|
||||
return Files.size(filePath);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Path buildManagedPath(String rootPath, String fileName) {
|
||||
Path root = DatabasePathUtil.normalize(rootPath);
|
||||
if (root == null) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份目录未配置");
|
||||
}
|
||||
return root.resolve(fileName).normalize();
|
||||
}
|
||||
|
||||
private String buildBaseFileName(DatabaseConnection connection, DatabaseOperationTask task) {
|
||||
return connection.getSchemaName() + "_" + task.getBackupStrategy().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private DatabaseOperationTask buildBackupTask(DatabaseBackupParam.CreateParam param, DatabaseConnection connection) {
|
||||
DatabaseOperationTask task = new DatabaseOperationTask();
|
||||
task.setId(DatabaseOpsIdUtil.uuid());
|
||||
@@ -246,9 +183,9 @@ public class DatabaseOperationTaskServiceImpl extends ServiceImpl<DatabaseOperat
|
||||
task.setConnectionId(connection.getId());
|
||||
task.setDbType(connection.getDbType());
|
||||
task.setOperationType(OperationTypeEnum.BACKUP.name());
|
||||
task.setBackupStrategy(resolveBackupStrategy(param.getBackupStrategy()));
|
||||
task.setBackupStrategy(resolveBackupStrategy(param.getBackupStrategy(), connection.getDbType()));
|
||||
task.setTaskStatus(TaskStatusEnum.WAITING.name());
|
||||
task.setSchemaName(StrUtil.blankToDefault(param.getSchemaName(), connection.getSchemaName()));
|
||||
task.setSchemaName(resolveSchemaName(param, connection));
|
||||
task.setTargetNamesJson(writeJson(param.getTargetNames()));
|
||||
task.setRequestParamJson(writeJsonWithoutPassword(param));
|
||||
task.setProgressPercent(BigDecimal.ZERO);
|
||||
@@ -259,15 +196,16 @@ public class DatabaseOperationTaskServiceImpl extends ServiceImpl<DatabaseOperat
|
||||
}
|
||||
|
||||
private void validateBackupParam(DatabaseBackupParam.CreateParam param, DatabaseConnection connection) {
|
||||
if (!DatabaseOpsConst.DB_TYPE_ORACLE.equals(connection.getDbType())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "一期仅支持 ORACLE");
|
||||
}
|
||||
if (param.getTargetNames() == null || param.getTargetNames().isEmpty()) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份表不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(StrUtil.blankToDefault(param.getSchemaName(), connection.getSchemaName()))) {
|
||||
if (DatabaseOpsConst.DB_TYPE_ORACLE.equals(connection.getDbType())
|
||||
&& StrUtil.isBlank(StrUtil.blankToDefault(param.getSchemaName(), connection.getSchemaName()))) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份 Schema 不能为空");
|
||||
}
|
||||
if (DatabaseOpsConst.DB_TYPE_MYSQL.equals(connection.getDbType()) && StrUtil.isBlank(connection.getDatabaseName())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "MYSQL 数据库名不能为空");
|
||||
}
|
||||
String backupMode = StrUtil.blankToDefault(param.getBackupMode(), BackupModeEnum.FULL_TABLE.name()).toUpperCase(Locale.ROOT);
|
||||
if (BackupModeEnum.TIME_RANGE.name().equals(backupMode)
|
||||
&& (param.getStartTime() == null || param.getEndTime() == null)) {
|
||||
@@ -282,22 +220,37 @@ public class DatabaseOperationTaskServiceImpl extends ServiceImpl<DatabaseOperat
|
||||
&& (param.getMaxFileSizeMb() == null || param.getMaxFileSizeMb() <= 0)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "按大小分片必须传入大于 0 的文件大小");
|
||||
}
|
||||
if (BackupStrategyEnum.JDBC_EXPORT.name().equals(resolveBackupStrategy(param.getBackupStrategy()))
|
||||
&& BackupModeEnum.TIME_RANGE.name().equals(backupMode)
|
||||
&& StrUtil.isBlank(param.getTimeColumn())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "JDBC 按时间备份必须传入时间字段");
|
||||
if (BackupModeEnum.TIME_RANGE.name().equals(backupMode) && StrUtil.isBlank(param.getTimeColumn())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "按时间备份必须传入时间字段");
|
||||
}
|
||||
resolveBackupStrategy(param.getBackupStrategy(), connection.getDbType());
|
||||
}
|
||||
|
||||
private String resolveBackupStrategy(String backupStrategy) {
|
||||
String value = StrUtil.blankToDefault(backupStrategy, BackupStrategyEnum.DATA_PUMP.name()).trim().toUpperCase(Locale.ROOT);
|
||||
private String resolveBackupStrategy(String backupStrategy, String dbType) {
|
||||
String value = StrUtil.blankToDefault(backupStrategy,
|
||||
DatabaseOpsConst.DB_TYPE_ORACLE.equals(dbType) ? BackupStrategyEnum.DATA_PUMP.name() : BackupStrategyEnum.JDBC_EXPORT.name())
|
||||
.trim()
|
||||
.toUpperCase(Locale.ROOT);
|
||||
try {
|
||||
return BackupStrategyEnum.valueOf(value).name();
|
||||
BackupStrategyEnum strategyEnum = BackupStrategyEnum.valueOf(value);
|
||||
if (DatabaseOpsConst.DB_TYPE_MYSQL.equals(dbType) && BackupStrategyEnum.DATA_PUMP == strategyEnum) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "MYSQL 不支持 DATA_PUMP");
|
||||
}
|
||||
return strategyEnum.name();
|
||||
} catch (BusinessException exception) {
|
||||
throw exception;
|
||||
} catch (Exception exception) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "不支持的备份策略:" + backupStrategy);
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveSchemaName(DatabaseBackupParam.CreateParam param, DatabaseConnection connection) {
|
||||
if (DatabaseOpsConst.DB_TYPE_MYSQL.equals(connection.getDbType())) {
|
||||
return StrUtil.blankToDefault(param.getSchemaName(), connection.getDatabaseName());
|
||||
}
|
||||
return StrUtil.blankToDefault(param.getSchemaName(), connection.getSchemaName());
|
||||
}
|
||||
|
||||
private void markRunning(DatabaseOperationTask task) {
|
||||
task.setTaskStatus(TaskStatusEnum.RUNNING.name());
|
||||
task.setStartedAt(LocalDateTime.now());
|
||||
@@ -315,6 +268,9 @@ public class DatabaseOperationTaskServiceImpl extends ServiceImpl<DatabaseOperat
|
||||
}
|
||||
|
||||
private void markFail(DatabaseOperationTask task, String message) {
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
task.setTaskStatus(TaskStatusEnum.FAIL.name());
|
||||
task.setResultMessage(message);
|
||||
task.setFinishedAt(LocalDateTime.now());
|
||||
@@ -322,6 +278,22 @@ public class DatabaseOperationTaskServiceImpl extends ServiceImpl<DatabaseOperat
|
||||
this.updateById(task);
|
||||
}
|
||||
|
||||
private void markCancelled(DatabaseOperationTask task, String message) {
|
||||
task.setTaskStatus(TaskStatusEnum.CANCELLED.name());
|
||||
task.setResultMessage(StrUtil.blankToDefault(message, "备份任务已停止"));
|
||||
task.setFinishedAt(LocalDateTime.now());
|
||||
task.setUpdateTime(LocalDateTime.now());
|
||||
this.updateById(task);
|
||||
}
|
||||
|
||||
private DatabaseOperationTask requireEnabledTask(String taskId) {
|
||||
DatabaseOperationTask task = this.getById(taskId);
|
||||
if (task == null || !Integer.valueOf(DatabaseOpsConst.STATE_ENABLED).equals(task.getState())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "任务不存在或已删除");
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
private String writeJson(Object value) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(value);
|
||||
@@ -337,6 +309,14 @@ public class DatabaseOperationTaskServiceImpl extends ServiceImpl<DatabaseOperat
|
||||
return writeJson(copy);
|
||||
}
|
||||
|
||||
private DatabaseBackupParam.CreateParam readCreateParam(String requestParamJson) {
|
||||
try {
|
||||
return objectMapper.readValue(requestParamJson, DatabaseBackupParam.CreateParam.class);
|
||||
} catch (Exception exception) {
|
||||
throw new BusinessException(CommonResponseEnum.JSON_CONVERT_EXCEPTION, exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseTaskCreateVO toCreateVO(DatabaseOperationTask task) {
|
||||
DatabaseTaskCreateVO vo = new DatabaseTaskCreateVO();
|
||||
vo.setTaskId(task.getId());
|
||||
|
||||
@@ -5,13 +5,9 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.gather.systemops.database.component.DataPumpCommandExecutor;
|
||||
import com.njcn.gather.systemops.database.component.JdbcExportComponent;
|
||||
import com.njcn.gather.systemops.database.component.OracleJdbcComponent;
|
||||
import com.njcn.gather.systemops.database.constant.DatabaseOpsConst;
|
||||
import com.njcn.gather.systemops.database.mapper.DatabaseRestoreRecordMapper;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.BackupStrategyEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.FileFormatEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.OperationTypeEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.RestoreModeEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.TaskStatusEnum;
|
||||
@@ -25,7 +21,9 @@ import com.njcn.gather.systemops.database.service.DatabaseBackupFileService;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseConnectionService;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseOperationTaskService;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseRestoreService;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseFileNameUtil;
|
||||
import com.njcn.gather.systemops.database.support.spi.DatabaseConnectionOperator;
|
||||
import com.njcn.gather.systemops.database.support.spi.DatabaseOperatorRegistry;
|
||||
import com.njcn.gather.systemops.database.support.spi.DatabaseRestoreOperator;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseOpsIdUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -34,7 +32,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -50,9 +47,7 @@ public class DatabaseRestoreServiceImpl extends ServiceImpl<DatabaseRestoreRecor
|
||||
private final DatabaseConnectionService databaseConnectionService;
|
||||
private final DatabaseOperationTaskService databaseOperationTaskService;
|
||||
private final DatabaseBackupFileService databaseBackupFileService;
|
||||
private final DataPumpCommandExecutor dataPumpCommandExecutor;
|
||||
private final JdbcExportComponent jdbcExportComponent;
|
||||
private final OracleJdbcComponent oracleJdbcComponent;
|
||||
private final DatabaseOperatorRegistry databaseOperatorRegistry;
|
||||
private final ObjectMapper objectMapper;
|
||||
@Resource(name = "dbmsTaskExecutorService")
|
||||
private ExecutorService dbmsTaskExecutorService;
|
||||
@@ -87,20 +82,8 @@ public class DatabaseRestoreServiceImpl extends ServiceImpl<DatabaseRestoreRecor
|
||||
DatabaseBackupFile backupFile = requireBackupFile(record.getBackupFileId());
|
||||
databaseBackupFileService.validateBackupFileReadable(backupFile);
|
||||
String password = databaseConnectionService.resolvePassword(connection, param.getTemporaryPassword());
|
||||
if (BackupStrategyEnum.DATA_PUMP.name().equals(backupFile.getBackupStrategy())) {
|
||||
DataPumpCommandExecutor.CommandResult result = dataPumpCommandExecutor.impdp(connection, password,
|
||||
backupFile.getDirectoryName(), backupFile.getDumpFileName(), buildRestoreLogName(task),
|
||||
record.getTableExistsAction());
|
||||
if (!Boolean.TRUE.equals(result.getSuccess())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "Data Pump 恢复失败:" + result.getOutput());
|
||||
}
|
||||
} else if (FileFormatEnum.CSV.name().equalsIgnoreCase(backupFile.getFileFormat())) {
|
||||
Path dataFilePath = databaseBackupFileService.resolveManagedPath(backupFile, backupFile.getFilePath());
|
||||
Path metadataFilePath = databaseBackupFileService.resolveManagedPath(backupFile, backupFile.getMetadataFilePath());
|
||||
jdbcExportComponent.importCsv(connection, password, dataFilePath, metadataFilePath, record.getRestoreMode(), record.getTargetSchemaName());
|
||||
} else {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "暂不支持的恢复文件格式:" + backupFile.getFileFormat());
|
||||
}
|
||||
DatabaseRestoreOperator operator = databaseOperatorRegistry.getRestoreOperator(connection.getDbType(), backupFile.getBackupStrategy());
|
||||
operator.executeRestore(task, record, backupFile, connection, password, param);
|
||||
record.setResultMessage("恢复任务执行成功");
|
||||
record.setUpdateTime(LocalDateTime.now());
|
||||
this.updateById(record);
|
||||
@@ -125,7 +108,8 @@ public class DatabaseRestoreServiceImpl extends ServiceImpl<DatabaseRestoreRecor
|
||||
}
|
||||
databaseBackupFileService.validateBackupFileReadable(backupFile);
|
||||
String password = databaseConnectionService.resolvePassword(connection, param.getTemporaryPassword());
|
||||
if (!Boolean.TRUE.equals(oracleJdbcComponent.test(connection, password).getSuccess())) {
|
||||
DatabaseConnectionOperator operator = databaseOperatorRegistry.getConnectionOperator(connection.getDbType());
|
||||
if (!Boolean.TRUE.equals(operator.test(connection, password).getSuccess())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "目标连接测试失败,不能创建恢复任务");
|
||||
}
|
||||
if (BackupStrategyEnum.DATA_PUMP.name().equals(backupFile.getBackupStrategy())) {
|
||||
@@ -148,7 +132,7 @@ public class DatabaseRestoreServiceImpl extends ServiceImpl<DatabaseRestoreRecor
|
||||
task.setOperationType(OperationTypeEnum.RESTORE.name());
|
||||
task.setBackupStrategy(backupFile.getBackupStrategy());
|
||||
task.setTaskStatus(TaskStatusEnum.WAITING.name());
|
||||
task.setSchemaName(StrUtil.blankToDefault(param.getTargetSchemaName(), connection.getSchemaName()));
|
||||
task.setSchemaName(resolveTargetSchemaName(param, connection));
|
||||
task.setTargetNamesJson(backupFile.getTargetNamesJson());
|
||||
task.setRequestParamJson(writeJsonWithoutPassword(param));
|
||||
task.setProgressPercent(BigDecimal.ZERO);
|
||||
@@ -168,7 +152,7 @@ public class DatabaseRestoreServiceImpl extends ServiceImpl<DatabaseRestoreRecor
|
||||
record.setConnectionId(connection.getId());
|
||||
record.setDbType(connection.getDbType());
|
||||
record.setRestoreMode(restoreMode);
|
||||
record.setTargetSchemaName(StrUtil.blankToDefault(param.getTargetSchemaName(), connection.getSchemaName()));
|
||||
record.setTargetSchemaName(resolveTargetSchemaName(param, connection));
|
||||
record.setTargetNamesJson(backupFile.getTargetNamesJson());
|
||||
record.setTableExistsAction(restoreMode);
|
||||
record.setOverwriteConfirmed(DatabaseOpsConst.CONFIRM_OVERWRITE.equals(param.getOverwriteConfirmText()) ? 1 : 0);
|
||||
@@ -178,6 +162,13 @@ public class DatabaseRestoreServiceImpl extends ServiceImpl<DatabaseRestoreRecor
|
||||
return record;
|
||||
}
|
||||
|
||||
private String resolveTargetSchemaName(DatabaseRestoreParam.CreateParam param, DatabaseConnection connection) {
|
||||
if (DatabaseOpsConst.DB_TYPE_MYSQL.equals(connection.getDbType())) {
|
||||
return StrUtil.blankToDefault(param.getTargetSchemaName(), connection.getDatabaseName());
|
||||
}
|
||||
return StrUtil.blankToDefault(param.getTargetSchemaName(), connection.getSchemaName());
|
||||
}
|
||||
|
||||
private DatabaseBackupFile requireBackupFile(String backupFileId) {
|
||||
DatabaseBackupFile backupFile = databaseBackupFileService.getById(backupFileId);
|
||||
if (backupFile == null || !Integer.valueOf(DatabaseOpsConst.STATE_ENABLED).equals(backupFile.getState())) {
|
||||
@@ -195,10 +186,6 @@ public class DatabaseRestoreServiceImpl extends ServiceImpl<DatabaseRestoreRecor
|
||||
}
|
||||
}
|
||||
|
||||
private String buildRestoreLogName(DatabaseOperationTask task) {
|
||||
return DatabaseFileNameUtil.appendTodayWithTask(task.getTaskNo() + "_restore.log", task.getTaskNo());
|
||||
}
|
||||
|
||||
private void markRunning(DatabaseOperationTask task) {
|
||||
task.setTaskStatus(TaskStatusEnum.RUNNING.name());
|
||||
task.setStartedAt(LocalDateTime.now());
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.njcn.gather.systemops.database.support.mysql;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.gather.systemops.database.constant.DatabaseOpsConst;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTableVO;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTestResultVO;
|
||||
import com.njcn.gather.systemops.database.support.spi.DatabaseConnectionOperator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* MySQL 连接能力实现。
|
||||
*/
|
||||
@Component
|
||||
public class MysqlConnectionOperator implements DatabaseConnectionOperator {
|
||||
|
||||
@Override
|
||||
public boolean support(String dbType) {
|
||||
return DatabaseOpsConst.DB_TYPE_MYSQL.equalsIgnoreCase(dbType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatabaseTestResultVO test(DatabaseConnection connection, String password) {
|
||||
DatabaseTestResultVO result = new DatabaseTestResultVO();
|
||||
try (Connection ignored = openConnection(connection, password)) {
|
||||
result.setSuccess(true);
|
||||
result.setMessage("连接成功");
|
||||
} catch (Exception exception) {
|
||||
result.setSuccess(false);
|
||||
result.setMessage(exception.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DatabaseTableVO> listTables(DatabaseConnection connection, String password, String schemaOrDatabaseName) throws Exception {
|
||||
String databaseName = StrUtil.blankToDefault(schemaOrDatabaseName, connection.getDatabaseName());
|
||||
String sql = "SELECT t.table_schema, t.table_name, t.auto_increment, t.update_time, "
|
||||
+ "t.data_length, t.engine, t.table_rows, t.table_comment, "
|
||||
+ "MAX(CASE WHEN c.extra LIKE '%auto_increment%' THEN 1 ELSE 0 END) AS has_auto_increment "
|
||||
+ "FROM information_schema.tables t "
|
||||
+ "LEFT JOIN information_schema.columns c "
|
||||
+ "ON t.table_schema = c.table_schema AND t.table_name = c.table_name "
|
||||
+ "WHERE t.table_schema = ? AND t.table_type = 'BASE TABLE' "
|
||||
+ "GROUP BY t.table_schema, t.table_name, t.auto_increment, t.update_time, "
|
||||
+ "t.data_length, t.engine, t.table_rows, t.table_comment "
|
||||
+ "ORDER BY t.table_name";
|
||||
try (Connection jdbcConnection = openConnection(connection, password);
|
||||
PreparedStatement statement = jdbcConnection.prepareStatement(sql)) {
|
||||
statement.setString(1, databaseName);
|
||||
try (ResultSet resultSet = statement.executeQuery()) {
|
||||
List<DatabaseTableVO> result = new ArrayList<>();
|
||||
while (resultSet.next()) {
|
||||
DatabaseTableVO table = new DatabaseTableVO();
|
||||
table.setOwner(resultSet.getString("table_schema").toUpperCase(Locale.ROOT));
|
||||
table.setTableName(resultSet.getString("table_name"));
|
||||
if (resultSet.getInt("has_auto_increment") == 1) {
|
||||
fillAutoIncrement(table, defaultZero(getLongValue(resultSet, "auto_increment")));
|
||||
}
|
||||
Timestamp updateTime = resultSet.getTimestamp("update_time");
|
||||
table.setUpdateTime(updateTime == null ? null : updateTime.toLocalDateTime());
|
||||
table.setDataLength(getLongValue(resultSet, "data_length"));
|
||||
table.setEngine(resultSet.getString("engine"));
|
||||
table.setTableRows(getLongValue(resultSet, "table_rows"));
|
||||
table.setComments(resultSet.getString("table_comment"));
|
||||
result.add(table);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Long getLongValue(ResultSet resultSet, String columnName) throws Exception {
|
||||
long value = resultSet.getLong(columnName);
|
||||
return resultSet.wasNull() ? null : value;
|
||||
}
|
||||
|
||||
private Long defaultZero(Long value) {
|
||||
return value == null ? 0L : value;
|
||||
}
|
||||
|
||||
private void fillAutoIncrement(DatabaseTableVO table, Long autoIncrement) {
|
||||
table.setAutoIncrementValue(autoIncrement);
|
||||
table.setAutoIncrement(autoIncrement);
|
||||
}
|
||||
|
||||
private Connection openConnection(DatabaseConnection connection, String password) throws Exception {
|
||||
if (StrUtil.isBlank(password)) {
|
||||
throw new IllegalArgumentException("数据库密码不能为空");
|
||||
}
|
||||
return DriverManager.getConnection(MysqlJdbcUrlUtil.build(connection), connection.getUsername(), password);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.njcn.gather.systemops.database.support.mysql;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.gather.systemops.database.component.JdbcExportComponent;
|
||||
import com.njcn.gather.systemops.database.config.DbmsProperties;
|
||||
import com.njcn.gather.systemops.database.mapper.DatabaseOperationTaskMapper;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.BackupModeEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.BackupStrategyEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.FileFormatEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.TaskStatusEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseBackupParam;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseBackupFile;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseOperationTask;
|
||||
import com.njcn.gather.systemops.database.support.spi.DatabaseBackupOperator;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseChecksumUtil;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseFileNameUtil;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseOpsIdUtil;
|
||||
import com.njcn.gather.systemops.database.util.DatabasePathUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* MySQL JDBC_EXPORT 大数据量备份实现。
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MysqlJdbcExportBackupOperator implements DatabaseBackupOperator {
|
||||
|
||||
private final JdbcExportComponent jdbcExportComponent;
|
||||
private final DbmsProperties dbmsProperties;
|
||||
private final DatabaseOperationTaskMapper databaseOperationTaskMapper;
|
||||
|
||||
@Override
|
||||
public boolean support(String dbType, String backupStrategy) {
|
||||
return "MYSQL".equalsIgnoreCase(dbType) && BackupStrategyEnum.JDBC_EXPORT.name().equals(backupStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatabaseBackupFile executeBackup(DatabaseOperationTask task, DatabaseConnection connection, String password,
|
||||
DatabaseBackupParam.CreateParam param) throws Exception {
|
||||
Path backupDirectory = buildManagedPath(dbmsProperties.getBackup().getStoragePath(), task.getTaskNo());
|
||||
String metadataFileName = DatabaseFileNameUtil.appendTodayWithTask("mysql_jdbc_export_metadata.json", task.getTaskNo());
|
||||
Path metadataFilePath = backupDirectory.resolve(metadataFileName).normalize();
|
||||
int fetchSize = positiveOrDefault(dbmsProperties.getBackup().getMysqlFetchSize(), 1000);
|
||||
long maxPartBytes = resolveMaxPartBytes(param);
|
||||
try (Connection jdbcConnection = DriverManager.getConnection(MysqlJdbcUrlUtil.build(connection), connection.getUsername(), password)) {
|
||||
jdbcExportComponent.exportMysqlCsvV2(jdbcConnection, connection.getDatabaseName(), task.getTaskNo(), param,
|
||||
backupDirectory, metadataFilePath, fetchSize, maxPartBytes, () -> isTaskCancelled(task.getId()));
|
||||
} catch (Exception exception) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, exception.getMessage() + ",导出目录:" + backupDirectory);
|
||||
}
|
||||
return buildBackupFile(task, connection, param, backupDirectory, metadataFilePath);
|
||||
}
|
||||
|
||||
private boolean isTaskCancelled(String taskId) {
|
||||
DatabaseOperationTask task = databaseOperationTaskMapper.selectById(taskId);
|
||||
return task != null && TaskStatusEnum.CANCELLED.name().equals(task.getTaskStatus());
|
||||
}
|
||||
|
||||
private DatabaseBackupFile buildBackupFile(DatabaseOperationTask task, DatabaseConnection connection,
|
||||
DatabaseBackupParam.CreateParam param, Path backupDirectory,
|
||||
Path metadataFilePath) throws Exception {
|
||||
if (!Files.exists(metadataFilePath)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份元数据文件未生成");
|
||||
}
|
||||
DatabaseBackupFile file = new DatabaseBackupFile();
|
||||
file.setId(DatabaseOpsIdUtil.uuid());
|
||||
file.setTaskId(task.getId());
|
||||
file.setConnectionId(connection.getId());
|
||||
file.setDbType(connection.getDbType());
|
||||
file.setBackupStrategy(task.getBackupStrategy());
|
||||
file.setFileFormat(FileFormatEnum.CSV.name());
|
||||
file.setSchemaName(task.getSchemaName());
|
||||
file.setTargetNamesJson(task.getTargetNamesJson());
|
||||
file.setBackupMode(StrUtil.blankToDefault(param.getBackupMode(), BackupModeEnum.FULL_TABLE.name()).toUpperCase(Locale.ROOT));
|
||||
file.setBackupStartTime(param.getStartTime());
|
||||
file.setBackupEndTime(param.getEndTime());
|
||||
file.setTimeColumn(param.getTimeColumn());
|
||||
file.setDirectoryName(null);
|
||||
file.setDumpFileName(null);
|
||||
file.setLogFileName(null);
|
||||
file.setFileName(backupDirectory.getFileName().toString());
|
||||
file.setFilePath(backupDirectory.toString());
|
||||
file.setLogFilePath(null);
|
||||
file.setMetadataFilePath(metadataFilePath.toString());
|
||||
file.setFileSize(readDirectoryFileSize(backupDirectory));
|
||||
file.setChecksum(DatabaseChecksumUtil.sha256(metadataFilePath));
|
||||
file.setState(1);
|
||||
file.setCreateTime(LocalDateTime.now());
|
||||
file.setUpdateTime(LocalDateTime.now());
|
||||
return file;
|
||||
}
|
||||
|
||||
private long resolveMaxPartBytes(DatabaseBackupParam.CreateParam param) {
|
||||
Integer maxFileSizeMb = param.getMaxFileSizeMb();
|
||||
if (maxFileSizeMb == null || maxFileSizeMb <= 0) {
|
||||
maxFileSizeMb = positiveOrDefault(dbmsProperties.getBackup().getDefaultMaxFileSizeMb(), 512);
|
||||
}
|
||||
return maxFileSizeMb.longValue() * 1024L * 1024L;
|
||||
}
|
||||
|
||||
private int positiveOrDefault(Integer value, int defaultValue) {
|
||||
return value == null || value <= 0 ? defaultValue : value;
|
||||
}
|
||||
|
||||
private Long readDirectoryFileSize(Path directory) {
|
||||
try {
|
||||
if (directory != null && Files.exists(directory) && Files.isDirectory(directory)) {
|
||||
final long[] total = new long[]{0L};
|
||||
try (Stream<Path> paths = Files.walk(directory)) {
|
||||
paths.filter(path -> Files.exists(path) && !Files.isDirectory(path))
|
||||
.forEach(path -> {
|
||||
try {
|
||||
total[0] += Files.size(path);
|
||||
} catch (Exception ignored) {
|
||||
// 忽略单个文件大小读取失败,避免影响备份记录生成。
|
||||
}
|
||||
});
|
||||
}
|
||||
return total[0];
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Path buildManagedPath(String rootPath, String directoryName) {
|
||||
Path root = DatabasePathUtil.normalize(rootPath);
|
||||
if (root == null) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份目录未配置");
|
||||
}
|
||||
return root.resolve(directoryName).normalize();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.njcn.gather.systemops.database.support.mysql;
|
||||
|
||||
import com.njcn.gather.systemops.database.component.JdbcExportComponent;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.BackupStrategyEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseRestoreParam;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseBackupFile;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseOperationTask;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseRestoreRecord;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseBackupFileService;
|
||||
import com.njcn.gather.systemops.database.support.spi.DatabaseRestoreOperator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
|
||||
/**
|
||||
* MySQL JDBC_EXPORT 恢复实现。
|
||||
*/
|
||||
@Component
|
||||
public class MysqlJdbcExportRestoreOperator implements DatabaseRestoreOperator {
|
||||
|
||||
private final JdbcExportComponent jdbcExportComponent;
|
||||
private final DatabaseBackupFileService databaseBackupFileService;
|
||||
|
||||
public MysqlJdbcExportRestoreOperator(JdbcExportComponent jdbcExportComponent,
|
||||
DatabaseBackupFileService databaseBackupFileService) {
|
||||
this.jdbcExportComponent = jdbcExportComponent;
|
||||
this.databaseBackupFileService = databaseBackupFileService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean support(String dbType, String backupStrategy) {
|
||||
return "MYSQL".equalsIgnoreCase(dbType) && BackupStrategyEnum.JDBC_EXPORT.name().equals(backupStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeRestore(DatabaseOperationTask task, DatabaseRestoreRecord record, DatabaseBackupFile backupFile,
|
||||
DatabaseConnection connection, String password, DatabaseRestoreParam.CreateParam param) throws Exception {
|
||||
Path dataFilePath = databaseBackupFileService.resolveManagedPath(backupFile, backupFile.getFilePath());
|
||||
Path metadataFilePath = databaseBackupFileService.resolveManagedPath(backupFile, backupFile.getMetadataFilePath());
|
||||
try (Connection jdbcConnection = DriverManager.getConnection(MysqlJdbcUrlUtil.build(connection), connection.getUsername(), password)) {
|
||||
jdbcExportComponent.importCsv(jdbcConnection, dataFilePath, metadataFilePath,
|
||||
connection.getDbType(), record.getRestoreMode(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.njcn.gather.systemops.database.support.mysql;
|
||||
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||
|
||||
/**
|
||||
* MySQL JDBC URL 构造工具。
|
||||
*/
|
||||
public final class MysqlJdbcUrlUtil {
|
||||
|
||||
private MysqlJdbcUrlUtil() {
|
||||
}
|
||||
|
||||
public static String build(DatabaseConnection connection) {
|
||||
return "jdbc:mysql://" + connection.getHost() + ":" + connection.getPort() + "/" + connection.getDatabaseName()
|
||||
+ "?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai"
|
||||
+ "&useCursorFetch=true&connectTimeout=5000&socketTimeout=30000";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.njcn.gather.systemops.database.support.oracle;
|
||||
|
||||
import com.njcn.gather.systemops.database.component.OracleJdbcComponent;
|
||||
import com.njcn.gather.systemops.database.constant.DatabaseOpsConst;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTableVO;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTestResultVO;
|
||||
import com.njcn.gather.systemops.database.support.spi.DatabaseConnectionOperator;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Oracle 连接能力实现。
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class OracleConnectionOperator implements DatabaseConnectionOperator {
|
||||
|
||||
private final OracleJdbcComponent oracleJdbcComponent;
|
||||
|
||||
@Override
|
||||
public boolean support(String dbType) {
|
||||
return DatabaseOpsConst.DB_TYPE_ORACLE.equalsIgnoreCase(dbType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatabaseTestResultVO test(DatabaseConnection connection, String password) {
|
||||
return oracleJdbcComponent.test(connection, password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DatabaseTableVO> listTables(DatabaseConnection connection, String password, String schemaOrDatabaseName) throws Exception {
|
||||
return oracleJdbcComponent.listTables(connection, password, schemaOrDatabaseName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.njcn.gather.systemops.database.support.oracle;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.gather.systemops.database.component.DataPumpCommandExecutor;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.BackupModeEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.BackupStrategyEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.FileFormatEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseBackupParam;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseBackupFile;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseOperationTask;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseBackupFileService;
|
||||
import com.njcn.gather.systemops.database.support.spi.DatabaseBackupOperator;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseChecksumUtil;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseFileNameUtil;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseOpsIdUtil;
|
||||
import com.njcn.gather.systemops.database.util.DatabasePathUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Oracle DATA_PUMP 备份实现。
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class OracleDataPumpBackupOperator implements DatabaseBackupOperator {
|
||||
|
||||
private final DataPumpCommandExecutor dataPumpCommandExecutor;
|
||||
private final DatabaseBackupFileService databaseBackupFileService;
|
||||
|
||||
@Override
|
||||
public boolean support(String dbType, String backupStrategy) {
|
||||
return "ORACLE".equalsIgnoreCase(dbType) && BackupStrategyEnum.DATA_PUMP.name().equals(backupStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatabaseBackupFile executeBackup(DatabaseOperationTask task, DatabaseConnection connection, String password,
|
||||
DatabaseBackupParam.CreateParam param) {
|
||||
String directoryName = StrUtil.blankToDefault(param.getDirectoryName(), connection.getDirectoryName());
|
||||
if (StrUtil.isBlank(directoryName)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "DATA_PUMP 备份需要 Oracle Directory 名称");
|
||||
}
|
||||
String baseName = buildBaseFileName(connection, task);
|
||||
String dumpFileName = DatabaseFileNameUtil.appendTodayWithTask(baseName + ".dmp", task.getTaskNo());
|
||||
String logFileName = DatabaseFileNameUtil.appendTodayWithTask(baseName + ".log", task.getTaskNo());
|
||||
DataPumpCommandExecutor.CommandResult commandResult = dataPumpCommandExecutor.expdp(connection, password,
|
||||
directoryName, dumpFileName, logFileName, param.getTargetNames());
|
||||
if (!Boolean.TRUE.equals(commandResult.getSuccess())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "Data Pump 执行失败:" + commandResult.getOutput());
|
||||
}
|
||||
if (StrUtil.isBlank(connection.getDirectoryPath())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "Data Pump 备份需要配置可管理的 directoryPath");
|
||||
}
|
||||
Path dumpPath = buildManagedPath(connection.getDirectoryPath(), dumpFileName);
|
||||
Path logPath = buildManagedPath(connection.getDirectoryPath(), logFileName);
|
||||
return buildBackupFile(task, connection, param, FileFormatEnum.DMP.name(), dumpFileName, dumpPath, logFileName, logPath, null);
|
||||
}
|
||||
|
||||
private DatabaseBackupFile buildBackupFile(DatabaseOperationTask task, DatabaseConnection connection,
|
||||
DatabaseBackupParam.CreateParam param, String fileFormat, String fileName,
|
||||
Path filePath, String logFileName, Path logFilePath, Path metadataFilePath) {
|
||||
if (filePath == null || !Files.exists(filePath)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份文件未生成");
|
||||
}
|
||||
DatabaseBackupFile file = new DatabaseBackupFile();
|
||||
file.setId(DatabaseOpsIdUtil.uuid());
|
||||
file.setTaskId(task.getId());
|
||||
file.setConnectionId(connection.getId());
|
||||
file.setDbType(connection.getDbType());
|
||||
file.setBackupStrategy(task.getBackupStrategy());
|
||||
file.setFileFormat(fileFormat);
|
||||
file.setSchemaName(task.getSchemaName());
|
||||
file.setTargetNamesJson(task.getTargetNamesJson());
|
||||
file.setBackupMode(StrUtil.blankToDefault(param.getBackupMode(), BackupModeEnum.FULL_TABLE.name()).toUpperCase(Locale.ROOT));
|
||||
file.setBackupStartTime(param.getStartTime());
|
||||
file.setBackupEndTime(param.getEndTime());
|
||||
file.setTimeColumn(param.getTimeColumn());
|
||||
file.setDirectoryName(StrUtil.blankToDefault(param.getDirectoryName(), connection.getDirectoryName()));
|
||||
file.setDumpFileName(FileFormatEnum.DMP.name().equals(fileFormat) ? fileName : null);
|
||||
file.setLogFileName(logFileName);
|
||||
file.setFileName(fileName);
|
||||
file.setFilePath(filePath.toString());
|
||||
file.setLogFilePath(logFilePath == null ? null : logFilePath.toString());
|
||||
file.setMetadataFilePath(metadataFilePath == null ? null : metadataFilePath.toString());
|
||||
file.setFileSize(readFileSize(filePath));
|
||||
file.setChecksum(DatabaseChecksumUtil.sha256(filePath));
|
||||
file.setState(1);
|
||||
file.setCreateTime(LocalDateTime.now());
|
||||
file.setUpdateTime(LocalDateTime.now());
|
||||
return file;
|
||||
}
|
||||
|
||||
private Long readFileSize(Path filePath) {
|
||||
try {
|
||||
if (filePath != null && Files.exists(filePath) && !Files.isDirectory(filePath)) {
|
||||
return Files.size(filePath);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Path buildManagedPath(String rootPath, String fileName) {
|
||||
Path root = DatabasePathUtil.normalize(rootPath);
|
||||
if (root == null) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份目录未配置");
|
||||
}
|
||||
return root.resolve(fileName).normalize();
|
||||
}
|
||||
|
||||
private String buildBaseFileName(DatabaseConnection connection, DatabaseOperationTask task) {
|
||||
return connection.getSchemaName() + "_" + task.getBackupStrategy().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.njcn.gather.systemops.database.support.oracle;
|
||||
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.gather.systemops.database.component.DataPumpCommandExecutor;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.BackupStrategyEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseRestoreParam;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseBackupFile;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseOperationTask;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseRestoreRecord;
|
||||
import com.njcn.gather.systemops.database.support.spi.DatabaseRestoreOperator;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseFileNameUtil;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Oracle DATA_PUMP 恢复实现。
|
||||
*/
|
||||
@Component
|
||||
public class OracleDataPumpRestoreOperator implements DatabaseRestoreOperator {
|
||||
|
||||
private final DataPumpCommandExecutor dataPumpCommandExecutor;
|
||||
|
||||
public OracleDataPumpRestoreOperator(DataPumpCommandExecutor dataPumpCommandExecutor) {
|
||||
this.dataPumpCommandExecutor = dataPumpCommandExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean support(String dbType, String backupStrategy) {
|
||||
return "ORACLE".equalsIgnoreCase(dbType) && BackupStrategyEnum.DATA_PUMP.name().equals(backupStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeRestore(DatabaseOperationTask task, DatabaseRestoreRecord record, DatabaseBackupFile backupFile,
|
||||
DatabaseConnection connection, String password, DatabaseRestoreParam.CreateParam param) {
|
||||
DataPumpCommandExecutor.CommandResult result = dataPumpCommandExecutor.impdp(connection, password,
|
||||
backupFile.getDirectoryName(), backupFile.getDumpFileName(), buildRestoreLogName(task),
|
||||
record.getTableExistsAction());
|
||||
if (!Boolean.TRUE.equals(result.getSuccess())) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "Data Pump 恢复失败:" + result.getOutput());
|
||||
}
|
||||
}
|
||||
|
||||
private String buildRestoreLogName(DatabaseOperationTask task) {
|
||||
return DatabaseFileNameUtil.appendTodayWithTask(task.getTaskNo() + "_restore.log", task.getTaskNo());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.njcn.gather.systemops.database.support.oracle;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.gather.systemops.database.component.JdbcExportComponent;
|
||||
import com.njcn.gather.systemops.database.component.OracleJdbcComponent;
|
||||
import com.njcn.gather.systemops.database.config.DbmsProperties;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.BackupModeEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.BackupStrategyEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.FileFormatEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseBackupParam;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseBackupFile;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseOperationTask;
|
||||
import com.njcn.gather.systemops.database.support.spi.DatabaseBackupOperator;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseChecksumUtil;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseFileNameUtil;
|
||||
import com.njcn.gather.systemops.database.util.DatabaseOpsIdUtil;
|
||||
import com.njcn.gather.systemops.database.util.DatabasePathUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Connection;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Oracle JDBC_EXPORT 备份实现。
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class OracleJdbcExportBackupOperator implements DatabaseBackupOperator {
|
||||
|
||||
private final JdbcExportComponent jdbcExportComponent;
|
||||
private final OracleJdbcComponent oracleJdbcComponent;
|
||||
private final DbmsProperties dbmsProperties;
|
||||
|
||||
@Override
|
||||
public boolean support(String dbType, String backupStrategy) {
|
||||
return "ORACLE".equalsIgnoreCase(dbType) && BackupStrategyEnum.JDBC_EXPORT.name().equals(backupStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatabaseBackupFile executeBackup(DatabaseOperationTask task, DatabaseConnection connection, String password,
|
||||
DatabaseBackupParam.CreateParam param) throws Exception {
|
||||
String baseName = buildBaseFileName(connection, task);
|
||||
String fileName = DatabaseFileNameUtil.appendTodayWithTask(baseName + ".csv", task.getTaskNo());
|
||||
String metadataFileName = DatabaseFileNameUtil.appendTodayWithTask(baseName + "_metadata.json", task.getTaskNo());
|
||||
Path dataFilePath = buildManagedPath(dbmsProperties.getBackup().getStoragePath(), fileName);
|
||||
Path metadataFilePath = buildManagedPath(dbmsProperties.getBackup().getStoragePath(), metadataFileName);
|
||||
try (Connection jdbcConnection = oracleJdbcComponent.openConnection(connection, password)) {
|
||||
jdbcExportComponent.exportCsv(jdbcConnection, connection.getSchemaName(), param, dataFilePath, metadataFilePath);
|
||||
}
|
||||
return buildBackupFile(task, connection, param, fileName, dataFilePath, metadataFilePath);
|
||||
}
|
||||
|
||||
private DatabaseBackupFile buildBackupFile(DatabaseOperationTask task, DatabaseConnection connection,
|
||||
DatabaseBackupParam.CreateParam param, String fileName, Path filePath,
|
||||
Path metadataFilePath) {
|
||||
if (filePath == null || !Files.exists(filePath)) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份文件未生成");
|
||||
}
|
||||
DatabaseBackupFile file = new DatabaseBackupFile();
|
||||
file.setId(DatabaseOpsIdUtil.uuid());
|
||||
file.setTaskId(task.getId());
|
||||
file.setConnectionId(connection.getId());
|
||||
file.setDbType(connection.getDbType());
|
||||
file.setBackupStrategy(task.getBackupStrategy());
|
||||
file.setFileFormat(FileFormatEnum.CSV.name());
|
||||
file.setSchemaName(task.getSchemaName());
|
||||
file.setTargetNamesJson(task.getTargetNamesJson());
|
||||
file.setBackupMode(StrUtil.blankToDefault(param.getBackupMode(), BackupModeEnum.FULL_TABLE.name()).toUpperCase(Locale.ROOT));
|
||||
file.setBackupStartTime(param.getStartTime());
|
||||
file.setBackupEndTime(param.getEndTime());
|
||||
file.setTimeColumn(param.getTimeColumn());
|
||||
file.setDirectoryName(null);
|
||||
file.setDumpFileName(null);
|
||||
file.setLogFileName(null);
|
||||
file.setFileName(fileName);
|
||||
file.setFilePath(filePath.toString());
|
||||
file.setLogFilePath(null);
|
||||
file.setMetadataFilePath(metadataFilePath.toString());
|
||||
file.setFileSize(readFileSize(filePath));
|
||||
file.setChecksum(DatabaseChecksumUtil.sha256(filePath));
|
||||
file.setState(1);
|
||||
file.setCreateTime(LocalDateTime.now());
|
||||
file.setUpdateTime(LocalDateTime.now());
|
||||
return file;
|
||||
}
|
||||
|
||||
private Long readFileSize(Path filePath) {
|
||||
try {
|
||||
if (filePath != null && Files.exists(filePath) && !Files.isDirectory(filePath)) {
|
||||
return Files.size(filePath);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Path buildManagedPath(String rootPath, String fileName) {
|
||||
Path root = DatabasePathUtil.normalize(rootPath);
|
||||
if (root == null) {
|
||||
throw new BusinessException(CommonResponseEnum.FAIL, "备份目录未配置");
|
||||
}
|
||||
return root.resolve(fileName).normalize();
|
||||
}
|
||||
|
||||
private String buildBaseFileName(DatabaseConnection connection, DatabaseOperationTask task) {
|
||||
return connection.getSchemaName() + "_" + task.getBackupStrategy().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.njcn.gather.systemops.database.support.oracle;
|
||||
|
||||
import com.njcn.gather.systemops.database.component.JdbcExportComponent;
|
||||
import com.njcn.gather.systemops.database.component.OracleJdbcComponent;
|
||||
import com.njcn.gather.systemops.database.pojo.enums.BackupStrategyEnum;
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseRestoreParam;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseBackupFile;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseOperationTask;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseRestoreRecord;
|
||||
import com.njcn.gather.systemops.database.service.DatabaseBackupFileService;
|
||||
import com.njcn.gather.systemops.database.support.spi.DatabaseRestoreOperator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Connection;
|
||||
|
||||
/**
|
||||
* Oracle JDBC_EXPORT 恢复实现。
|
||||
*/
|
||||
@Component
|
||||
public class OracleJdbcExportRestoreOperator implements DatabaseRestoreOperator {
|
||||
|
||||
private final JdbcExportComponent jdbcExportComponent;
|
||||
private final OracleJdbcComponent oracleJdbcComponent;
|
||||
private final DatabaseBackupFileService databaseBackupFileService;
|
||||
|
||||
public OracleJdbcExportRestoreOperator(JdbcExportComponent jdbcExportComponent,
|
||||
OracleJdbcComponent oracleJdbcComponent,
|
||||
DatabaseBackupFileService databaseBackupFileService) {
|
||||
this.jdbcExportComponent = jdbcExportComponent;
|
||||
this.oracleJdbcComponent = oracleJdbcComponent;
|
||||
this.databaseBackupFileService = databaseBackupFileService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean support(String dbType, String backupStrategy) {
|
||||
return "ORACLE".equalsIgnoreCase(dbType) && BackupStrategyEnum.JDBC_EXPORT.name().equals(backupStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeRestore(DatabaseOperationTask task, DatabaseRestoreRecord record, DatabaseBackupFile backupFile,
|
||||
DatabaseConnection connection, String password, DatabaseRestoreParam.CreateParam param) throws Exception {
|
||||
Path dataFilePath = databaseBackupFileService.resolveManagedPath(backupFile, backupFile.getFilePath());
|
||||
Path metadataFilePath = databaseBackupFileService.resolveManagedPath(backupFile, backupFile.getMetadataFilePath());
|
||||
try (Connection jdbcConnection = oracleJdbcComponent.openConnection(connection, password)) {
|
||||
jdbcExportComponent.importCsv(jdbcConnection, dataFilePath, metadataFilePath,
|
||||
connection.getDbType(), record.getRestoreMode(), record.getTargetSchemaName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.njcn.gather.systemops.database.support.spi;
|
||||
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseBackupParam;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseBackupFile;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseOperationTask;
|
||||
|
||||
/**
|
||||
* 按数据库类型与备份策略隔离备份执行能力。
|
||||
*/
|
||||
public interface DatabaseBackupOperator {
|
||||
|
||||
boolean support(String dbType, String backupStrategy);
|
||||
|
||||
DatabaseBackupFile executeBackup(DatabaseOperationTask task, DatabaseConnection connection, String password,
|
||||
DatabaseBackupParam.CreateParam param) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.njcn.gather.systemops.database.support.spi;
|
||||
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTableVO;
|
||||
import com.njcn.gather.systemops.database.pojo.vo.DatabaseTestResultVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 按数据库类型隔离连接测试与表查询能力。
|
||||
*/
|
||||
public interface DatabaseConnectionOperator {
|
||||
|
||||
boolean support(String dbType);
|
||||
|
||||
DatabaseTestResultVO test(DatabaseConnection connection, String password);
|
||||
|
||||
List<DatabaseTableVO> listTables(DatabaseConnection connection, String password, String schemaOrDatabaseName) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.njcn.gather.systemops.database.support.spi;
|
||||
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据库能力路由注册器。
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DatabaseOperatorRegistry {
|
||||
|
||||
private final List<DatabaseConnectionOperator> connectionOperators;
|
||||
private final List<DatabaseBackupOperator> backupOperators;
|
||||
private final List<DatabaseRestoreOperator> restoreOperators;
|
||||
|
||||
public DatabaseConnectionOperator getConnectionOperator(String dbType) {
|
||||
return connectionOperators.stream()
|
||||
.filter(operator -> operator.support(dbType))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new BusinessException(CommonResponseEnum.FAIL, "暂不支持的数据库类型:" + dbType));
|
||||
}
|
||||
|
||||
public DatabaseBackupOperator getBackupOperator(String dbType, String backupStrategy) {
|
||||
return backupOperators.stream()
|
||||
.filter(operator -> operator.support(dbType, backupStrategy))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new BusinessException(CommonResponseEnum.FAIL,
|
||||
"暂不支持的备份能力:" + dbType + "/" + backupStrategy));
|
||||
}
|
||||
|
||||
public DatabaseRestoreOperator getRestoreOperator(String dbType, String backupStrategy) {
|
||||
return restoreOperators.stream()
|
||||
.filter(operator -> operator.support(dbType, backupStrategy))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new BusinessException(CommonResponseEnum.FAIL,
|
||||
"暂不支持的恢复能力:" + dbType + "/" + backupStrategy));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.njcn.gather.systemops.database.support.spi;
|
||||
|
||||
import com.njcn.gather.systemops.database.pojo.param.DatabaseRestoreParam;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseBackupFile;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseOperationTask;
|
||||
import com.njcn.gather.systemops.database.pojo.po.DatabaseRestoreRecord;
|
||||
|
||||
/**
|
||||
* 按数据库类型与备份策略隔离恢复执行能力。
|
||||
*/
|
||||
public interface DatabaseRestoreOperator {
|
||||
|
||||
boolean support(String dbType, String backupStrategy);
|
||||
|
||||
void executeRestore(DatabaseOperationTask task, DatabaseRestoreRecord record, DatabaseBackupFile backupFile,
|
||||
DatabaseConnection connection, String password, DatabaseRestoreParam.CreateParam param) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
CREATE TABLE IF NOT EXISTS `dbms_connection` (
|
||||
`id` VARCHAR(64) NOT NULL COMMENT '主键',
|
||||
`connection_name` VARCHAR(100) NOT NULL COMMENT '连接名称',
|
||||
`db_type` VARCHAR(32) NOT NULL DEFAULT 'ORACLE' COMMENT '数据库类型:ORACLE,后续可扩展 MYSQL、INFLUXDB',
|
||||
`host` VARCHAR(255) NOT NULL COMMENT '数据库主机地址',
|
||||
`port` INT NOT NULL COMMENT '数据库端口',
|
||||
`connect_type` VARCHAR(32) NULL COMMENT '连接类型:SERVICE_NAME、SID,Oracle 使用',
|
||||
`service_name` VARCHAR(128) NULL COMMENT '服务名,Oracle SERVICE_NAME 模式使用',
|
||||
`sid` VARCHAR(128) NULL COMMENT 'SID,Oracle SID 模式使用',
|
||||
`database_name` VARCHAR(128) NULL COMMENT '数据库名或实例名,预留给 MySQL 等数据库使用',
|
||||
`schema_name` VARCHAR(128) NULL COMMENT '默认 Schema,Oracle 使用',
|
||||
`username` VARCHAR(128) NOT NULL COMMENT '用户名',
|
||||
`password_cipher` VARCHAR(1000) NULL COMMENT '保存的数据库密码;为空表示不保存密码,执行时临时输入',
|
||||
`save_password` TINYINT NOT NULL DEFAULT 1 COMMENT '是否保存密码:0-否,1-是',
|
||||
`directory_name` VARCHAR(128) NULL COMMENT '默认数据库目录对象名称,Oracle Data Pump 使用',
|
||||
`directory_path` VARCHAR(500) NULL COMMENT '目录对象对应物理路径,仅用于展示和校验',
|
||||
`extra_config_json` JSON NULL COMMENT '扩展配置 JSON,用于保存不同数据库的差异配置',
|
||||
`remark` VARCHAR(500) NULL COMMENT '备注',
|
||||
`last_test_status` VARCHAR(32) NULL COMMENT '最近连接测试状态:SUCCESS、FAIL',
|
||||
`last_test_message` VARCHAR(1000) NULL COMMENT '最近连接测试结果说明',
|
||||
`last_test_time` DATETIME NULL COMMENT '最近连接测试时间',
|
||||
`state` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0-删除,1-正常',
|
||||
`create_by` VARCHAR(64) NULL COMMENT '创建人',
|
||||
`create_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_by` VARCHAR(64) NULL COMMENT '更新人',
|
||||
`update_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_dbms_connection_state` (`state`),
|
||||
KEY `idx_dbms_connection_db_type` (`db_type`),
|
||||
KEY `idx_dbms_connection_name` (`connection_name`),
|
||||
KEY `idx_dbms_connection_schema` (`schema_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据库连接配置表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `dbms_operation_task` (
|
||||
`id` VARCHAR(64) NOT NULL COMMENT '主键',
|
||||
`task_no` VARCHAR(64) NOT NULL COMMENT '任务编号',
|
||||
`connection_id` VARCHAR(64) NOT NULL COMMENT '数据库连接配置 ID',
|
||||
`db_type` VARCHAR(32) NOT NULL COMMENT '数据库类型:ORACLE,后续可扩展 MYSQL、INFLUXDB',
|
||||
`operation_type` VARCHAR(32) NOT NULL COMMENT '操作类型:BACKUP、RESTORE、DELETE',
|
||||
`backup_strategy` VARCHAR(32) NULL COMMENT '备份策略:DATA_PUMP、JDBC_EXPORT',
|
||||
`task_status` VARCHAR(32) NOT NULL DEFAULT 'WAITING' COMMENT '任务状态:WAITING、RUNNING、SUCCESS、FAIL、CANCELLED',
|
||||
`schema_name` VARCHAR(128) NULL COMMENT '操作 Schema',
|
||||
`target_names_json` JSON NULL COMMENT '操作对象名称列表 JSON,例如表名列表',
|
||||
`request_param_json` JSON NULL COMMENT '请求参数快照 JSON,不保存运行时密码',
|
||||
`result_message` VARCHAR(2000) NULL COMMENT '执行结果或失败原因',
|
||||
`progress_percent` DECIMAL(5,2) NOT NULL DEFAULT 0.00 COMMENT '任务进度百分比',
|
||||
`started_at` DATETIME NULL COMMENT '任务开始时间',
|
||||
`finished_at` DATETIME NULL COMMENT '任务结束时间',
|
||||
`state` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0-删除,1-正常',
|
||||
`create_by` VARCHAR(64) NULL COMMENT '创建人',
|
||||
`create_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_by` VARCHAR(64) NULL COMMENT '更新人',
|
||||
`update_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_dbms_operation_task_no` (`task_no`),
|
||||
KEY `idx_dbms_operation_connection` (`connection_id`),
|
||||
KEY `idx_dbms_operation_db_type` (`db_type`),
|
||||
KEY `idx_dbms_operation_type_status` (`operation_type`, `task_status`),
|
||||
KEY `idx_dbms_operation_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据库运维任务表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `dbms_backup_file` (
|
||||
`id` VARCHAR(64) NOT NULL COMMENT '主键',
|
||||
`task_id` VARCHAR(64) NOT NULL COMMENT '备份任务 ID',
|
||||
`connection_id` VARCHAR(64) NOT NULL COMMENT '数据库连接配置 ID',
|
||||
`db_type` VARCHAR(32) NOT NULL COMMENT '数据库类型:ORACLE,后续可扩展 MYSQL、INFLUXDB',
|
||||
`backup_strategy` VARCHAR(32) NOT NULL COMMENT '备份策略:DATA_PUMP、JDBC_EXPORT',
|
||||
`file_format` VARCHAR(32) NOT NULL COMMENT '文件格式:DMP、SQL、CSV',
|
||||
`schema_name` VARCHAR(128) NULL COMMENT '备份 Schema',
|
||||
`target_names_json` JSON NULL COMMENT '备份对象名称列表 JSON,例如表名列表',
|
||||
`backup_mode` VARCHAR(32) NOT NULL DEFAULT 'FULL_TABLE' COMMENT '备份模式:FULL_TABLE、TIME_RANGE、SIZE_SPLIT',
|
||||
`backup_start_time` DATETIME NULL COMMENT '按时间备份开始时间',
|
||||
`backup_end_time` DATETIME NULL COMMENT '按时间备份结束时间',
|
||||
`time_column` VARCHAR(128) NULL COMMENT '按时间备份使用的时间字段',
|
||||
`directory_name` VARCHAR(128) NULL COMMENT '数据库目录对象名称,Oracle Data Pump 使用',
|
||||
`dump_file_name` VARCHAR(255) NULL COMMENT 'Data Pump dump 文件名',
|
||||
`log_file_name` VARCHAR(255) NULL COMMENT 'Data Pump log 文件名',
|
||||
`file_name` VARCHAR(255) NOT NULL COMMENT '主备份文件名,需包含 _yyyyMMdd',
|
||||
`file_path` VARCHAR(1000) NOT NULL COMMENT '服务端记录的备份文件路径或目录对象映射路径',
|
||||
`log_file_path` VARCHAR(1000) NULL COMMENT '备份日志文件路径',
|
||||
`metadata_file_path` VARCHAR(1000) NULL COMMENT 'JDBC_EXPORT 元数据文件路径',
|
||||
`file_size` BIGINT NULL COMMENT '文件大小,单位字节',
|
||||
`checksum` VARCHAR(128) NULL COMMENT '文件校验值',
|
||||
`state` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0-删除,1-正常',
|
||||
`create_by` VARCHAR(64) NULL COMMENT '创建人',
|
||||
`create_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_by` VARCHAR(64) NULL COMMENT '更新人',
|
||||
`update_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_dbms_backup_task` (`task_id`),
|
||||
KEY `idx_dbms_backup_connection` (`connection_id`),
|
||||
KEY `idx_dbms_backup_db_type` (`db_type`),
|
||||
KEY `idx_dbms_backup_strategy` (`backup_strategy`),
|
||||
KEY `idx_dbms_backup_create_time` (`create_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据库备份文件记录表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `dbms_restore_record` (
|
||||
`id` VARCHAR(64) NOT NULL COMMENT '主键',
|
||||
`task_id` VARCHAR(64) NOT NULL COMMENT '恢复任务 ID',
|
||||
`backup_file_id` VARCHAR(64) NOT NULL COMMENT '备份文件 ID',
|
||||
`connection_id` VARCHAR(64) NOT NULL COMMENT '目标数据库连接配置 ID',
|
||||
`db_type` VARCHAR(32) NOT NULL COMMENT '数据库类型:ORACLE,后续可扩展 MYSQL、INFLUXDB',
|
||||
`restore_mode` VARCHAR(32) NOT NULL DEFAULT 'SKIP' COMMENT '恢复模式:SKIP、APPEND、TRUNCATE、REPLACE',
|
||||
`target_schema_name` VARCHAR(128) NULL COMMENT '目标 Schema',
|
||||
`target_names_json` JSON NULL COMMENT '恢复对象名称列表 JSON,例如表名列表',
|
||||
`table_exists_action` VARCHAR(32) NULL COMMENT 'Data Pump TABLE_EXISTS_ACTION:SKIP、APPEND、TRUNCATE、REPLACE',
|
||||
`overwrite_confirmed` TINYINT NOT NULL DEFAULT 0 COMMENT '是否已确认覆盖类操作:0-否,1-是',
|
||||
`result_message` VARCHAR(2000) NULL COMMENT '恢复结果说明',
|
||||
`state` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0-删除,1-正常',
|
||||
`create_by` VARCHAR(64) NULL COMMENT '创建人',
|
||||
`create_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_by` VARCHAR(64) NULL COMMENT '更新人',
|
||||
`update_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_dbms_restore_task` (`task_id`),
|
||||
KEY `idx_dbms_restore_backup_file` (`backup_file_id`),
|
||||
KEY `idx_dbms_restore_connection` (`connection_id`),
|
||||
KEY `idx_dbms_restore_db_type` (`db_type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据库恢复记录表';
|
||||
@@ -0,0 +1,14 @@
|
||||
INSERT INTO `cn_tool`.`sys_function`
|
||||
(`Id`, `Pid`, `Pids`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `Type`, `Remark`, `State`, `Create_By`, `Create_Time`, `Update_By`, `Update_Time`)
|
||||
VALUES
|
||||
('9f3b2c7a6e8d4b91a5c0f2d7e6a3b841', '0', '0', '系统运维', 'systemOps', '/systemOps', '/systemOps/index', 'Aim', 50, 0, '系统运维', 1, 'f8516cc81d964cd8b4b771a3b3985cd4', '2026-05-20 10:00:00', 'f8516cc81d964cd8b4b771a3b3985cd4', '2026-05-20 10:00:00');
|
||||
|
||||
INSERT INTO `cn_tool`.`sys_function`
|
||||
(`Id`, `Pid`, `Pids`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `Type`, `Remark`, `State`, `Create_By`, `Create_Time`, `Update_By`, `Update_Time`)
|
||||
VALUES
|
||||
('2a7e5d9c1f4b4386b0c9e6f3a8d21754', '9f3b2c7a6e8d4b91a5c0f2d7e6a3b841', '0,9f3b2c7a6e8d4b91a5c0f2d7e6a3b841', '数据库监控', 'database', '/systemOps/database', '/systemOps/database/index', 'Monitor', 100, 0, '数据库监控', 1, 'f8516cc81d964cd8b4b771a3b3985cd4', '2026-05-20 10:10:00', 'f8516cc81d964cd8b4b771a3b3985cd4', '2026-05-20 10:10:00');
|
||||
|
||||
INSERT INTO `cn_tool`.`sys_function`
|
||||
(`Id`, `Pid`, `Pids`, `Name`, `Code`, `Path`, `Component`, `Icon`, `Sort`, `Type`, `Remark`, `State`, `Create_By`, `Create_Time`, `Update_By`, `Update_Time`)
|
||||
VALUES
|
||||
('7c6d4a1e9b2f43c8a5e0d3f6b9c21875', '9f3b2c7a6e8d4b91a5c0f2d7e6a3b841', '0,9f3b2c7a6e8d4b91a5c0f2d7e6a3b841', '系统部署', 'deploy', '/systemOps/deploy', '/systemOps/deploy/index', 'Upload', 110, 0, '系统部署', 1, 'f8516cc81d964cd8b4b771a3b3985cd4', '2026-05-20 10:20:00', 'f8516cc81d964cd8b4b771a3b3985cd4', '2026-05-20 10:20:00');
|
||||
@@ -223,7 +223,8 @@ public class AddDataValueGenerator {
|
||||
if (baseValue == null) {
|
||||
throw new IllegalStateException("派生字段缺少主值:" + column);
|
||||
}
|
||||
double factor = noise(state.sharedSeed + column.hashCode(), 0.01D, 0.05D);
|
||||
String baseColumn = resolveDerivedBaseColumn(column, metricType);
|
||||
double factor = noise(state.sharedSeed + baseColumn.hashCode(), 0.01D, 0.05D);
|
||||
double delta = Math.max(Math.abs(baseValue) * factor, 0.005D);
|
||||
double value;
|
||||
if (MetricType.MAX.equals(metricType)) {
|
||||
@@ -239,6 +240,16 @@ public class AddDataValueGenerator {
|
||||
return round(value, 4);
|
||||
}
|
||||
|
||||
private String resolveDerivedBaseColumn(String column, MetricType metricType) {
|
||||
if (MetricType.MAX.equals(metricType)) {
|
||||
return removeSuffix(column, SUFFIX_MAX);
|
||||
}
|
||||
if (MetricType.MIN.equals(metricType)) {
|
||||
return removeSuffix(column, SUFFIX_MIN);
|
||||
}
|
||||
return removeSuffix(column, SUFFIX_CP95);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建同源基础状态。
|
||||
*
|
||||
|
||||
@@ -25,4 +25,24 @@ class AddDataValueGeneratorTest {
|
||||
|
||||
Assertions.assertEquals(0, row.get(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldKeepStatValuesOrderedForSameTimeAndMetric() {
|
||||
AddDataValueGenerator generator = new AddDataValueGenerator();
|
||||
AddDataTableDefinition definition = new AddDataTableDefinition("data_v",
|
||||
Arrays.asList("TIMEID", "LINEID", "PHASIC_TYPE", "QUALITYFLAG",
|
||||
"RMS", "RMS_MAX", "RMS_CP95", "RMS_MIN"),
|
||||
Arrays.asList("A"), 100, AddDataTableDefinition.TimeAxisType.REQUEST_INTERVAL);
|
||||
|
||||
List<Object> row = generator.generateRow(definition, "line-001",
|
||||
LocalDateTime.of(2026, 5, 18, 10, 0, 0), "A");
|
||||
|
||||
double avg = ((Number) row.get(4)).doubleValue();
|
||||
double max = ((Number) row.get(5)).doubleValue();
|
||||
double cp95 = ((Number) row.get(6)).doubleValue();
|
||||
double min = ((Number) row.get(7)).doubleValue();
|
||||
Assertions.assertTrue(max >= cp95, "MAX should be greater than or equal to CP95");
|
||||
Assertions.assertTrue(cp95 >= avg, "CP95 should be greater than or equal to AVG");
|
||||
Assertions.assertTrue(avg >= min, "AVG should be greater than or equal to MIN");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ public class JsonToXmlConversionService {
|
||||
MappingDocument mappingDocument = objectMapper.readValue(mappingJson, MappingDocument.class);
|
||||
return buildXmlFromMapping(mappingDocument, templateStream, ruleStreams, indexMapping, methodDescribeList);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从MappingDocument构建XML内容
|
||||
*/
|
||||
@@ -102,13 +102,13 @@ public class JsonToXmlConversionService {
|
||||
List<InputStream> ruleStreams,
|
||||
IcdToXmlMappingService.IndexMappingConfig indexMapping,
|
||||
List<String> methodDescribeList) throws Exception {
|
||||
|
||||
// 读取模板
|
||||
String templateContent = readInputStreamToString(templateStream);
|
||||
|
||||
//填充IED信息
|
||||
String xmlContent = fillIedInfo(templateContent, mappingDocument);
|
||||
|
||||
//填充ReportControls信息
|
||||
xmlContent = fillReportControlsFromMapping(xmlContent, mappingDocument);
|
||||
|
||||
//填充do da信息
|
||||
xmlContent = applyRulesFromMapping(xmlContent, mappingDocument, ruleStreams, indexMapping, methodDescribeList);
|
||||
|
||||
return xmlContent;
|
||||
@@ -192,6 +192,68 @@ public class JsonToXmlConversionService {
|
||||
reportStatBuilder.toString().replace("$", "\\$").replace("[", "\\[").replace("]", "\\]")
|
||||
);
|
||||
|
||||
reportStatBuilder = new StringBuilder();
|
||||
reportStatBuilder.append("\t\t<ReportReal>\n");
|
||||
|
||||
for (var reportItem : mappingDocument.getReportMap()) {
|
||||
if (reportItem.getName() != null && !reportItem.getName().isEmpty()) {
|
||||
String name = reportItem.getName();
|
||||
|
||||
if (name.contains("urcbFluc") ||
|
||||
name.contains("urcbStHarm") ||
|
||||
name.contains("urcbStIHarm") ||
|
||||
name.contains("urcbStMMXU") ||
|
||||
name.contains("urcbStMSQI") ||
|
||||
name.contains("urcbPLT") ||
|
||||
name.contains("urcbPST") ||
|
||||
name.contains("urcbFlicker") ||
|
||||
name.contains("urcbStatistic")||
|
||||
name.contains("urcbRealData")) {
|
||||
|
||||
String reportControlStr = buildReportControlString(reportItem);
|
||||
reportStatBuilder.append("\t\t\t<Report ReportControl=\"")
|
||||
.append(escapeXml(reportControlStr))
|
||||
.append("\" />\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reportStatBuilder.append("\t\t</ReportReal>");
|
||||
|
||||
xmlContent = xmlContent.replaceAll(
|
||||
"<ReportReal>[\\s\\S]*?</ReportReal>",
|
||||
reportStatBuilder.toString().replace("$", "\\$").replace("[", "\\[").replace("]", "\\]")
|
||||
);
|
||||
|
||||
reportStatBuilder = new StringBuilder();
|
||||
reportStatBuilder.append("\t\t<ReportEvent>\n");
|
||||
|
||||
for (var reportItem : mappingDocument.getReportMap()) {
|
||||
if (reportItem.getName() != null && !reportItem.getName().isEmpty()) {
|
||||
String name = reportItem.getName();
|
||||
|
||||
if (name.contains("QVVR") ||
|
||||
name.contains("RDRE") ||
|
||||
name.contains("GGIO")) {
|
||||
|
||||
String reportControlStr = buildEventReportControlString(reportItem);
|
||||
reportStatBuilder.append("\t\t\t<Report ReportControl=\"")
|
||||
.append(escapeXml(reportControlStr))
|
||||
.append("\" />\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reportStatBuilder.append("\t\t</ReportEvent>");
|
||||
|
||||
xmlContent = xmlContent.replaceAll(
|
||||
"<ReportEvent>[\\s\\S]*?</ReportEvent>",
|
||||
reportStatBuilder.toString().replace("$", "\\$").replace("[", "\\[").replace("]", "\\]")
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
return xmlContent;
|
||||
}
|
||||
|
||||
@@ -202,8 +264,10 @@ public class JsonToXmlConversionService {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("LLN0$").append(rc.getBuffered() != null && rc.getBuffered().equals("BR") ? "BR$" : "RP$").append(rc.getName());
|
||||
|
||||
if(rc.getName().contains("PLT") || rc.getName().contains("Flicker") || rc.getName().contains("PST") || rc.getName().contains("Fluc")){
|
||||
if(rc.getName().contains("urcb")){
|
||||
sb.append(",3");
|
||||
}
|
||||
else if(rc.getName().contains("PLT") || rc.getName().contains("Flicker") || rc.getName().contains("PST") || rc.getName().contains("Fluc")){
|
||||
sb.append(",600");
|
||||
}else{
|
||||
sb.append(",60");
|
||||
@@ -227,15 +291,53 @@ public class JsonToXmlConversionService {
|
||||
|
||||
if(rc.getName().contains("PLT") || rc.getName().contains("Flicker")){
|
||||
sb.append(",1");
|
||||
}else if(rc.getName().contains("PST") || rc.getName().contains("Fluc")){
|
||||
}else if(rc.getName().contains("PST") || rc.getName().contains("Fluc")|| rc.getName().contains("RealData")){
|
||||
sb.append(",2");
|
||||
} else {
|
||||
}else {
|
||||
sb.append(",0");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 构建EventReportControl字符串
|
||||
*/
|
||||
private String buildEventReportControlString(ReportMapItem rc) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("LLN0$").append(rc.getBuffered() != null && rc.getBuffered().equals("BR") ? "BR$" : "RP$").append(rc.getName());
|
||||
if(rc.getName().contains("urcb")){
|
||||
sb.append(",3");
|
||||
}
|
||||
else if(rc.getName().contains("PLT") || rc.getName().contains("Flicker") || rc.getName().contains("PST") || rc.getName().contains("Fluc")){
|
||||
sb.append(",600");
|
||||
}else{
|
||||
sb.append(",60");
|
||||
}
|
||||
sb.append(",1");
|
||||
sb.append(",0");
|
||||
sb.append(",0");
|
||||
sb.append(",0");
|
||||
sb.append(",0");
|
||||
sb.append(",yes");
|
||||
sb.append(",1");
|
||||
sb.append(",1");
|
||||
sb.append(",1");
|
||||
sb.append(",1");
|
||||
sb.append(",1");
|
||||
sb.append(",0");
|
||||
sb.append(",1");
|
||||
sb.append(",1");
|
||||
sb.append(",1");
|
||||
sb.append(",8");
|
||||
sb.append(",1");
|
||||
sb.append(",0");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从MappingDocument应用规则
|
||||
*/
|
||||
@@ -244,9 +346,9 @@ public class JsonToXmlConversionService {
|
||||
List<InputStream> ruleStreams,
|
||||
IcdToXmlMappingService.IndexMappingConfig indexMapping,
|
||||
List<String> methodDescribeList) throws Exception {
|
||||
|
||||
//合并所有规则文件
|
||||
var mergedRules = mergeAllRulesDesc(ruleStreams, indexMapping);
|
||||
|
||||
//读取mappingDocument
|
||||
var mappingMetrics = extractMetricsFromMapping(mappingDocument);
|
||||
|
||||
addMethodDescribe(methodDescribeList, "========== 开始从JSON匹配规则 ==========");
|
||||
@@ -401,18 +503,14 @@ public class JsonToXmlConversionService {
|
||||
String line;
|
||||
String currentGroup = "";
|
||||
|
||||
java.util.regex.Pattern valueRulePattern = java.util.regex.Pattern.compile(
|
||||
"<Value\\s+" +
|
||||
"name=\"([^\"]+)\"\\s+" +
|
||||
"desc=\"([^\"]+)\"\\s+" +
|
||||
"type=\"([^\"]+)\"\\s+" +
|
||||
"(?:DO=\"([^\"]*)\")?\\s*" +
|
||||
"(?:DA=\"([^\"]*)\")?\\s*" +
|
||||
"(?:BaseFlag=\"([^\"]*)\")?\\s*" +
|
||||
"(?:LimitUp=\"([^\"]*)\")?\\s*" +
|
||||
"(?:LimitDown=\"([^\"]*)\")?\\s*" +
|
||||
"(?:Coefficient=\"([^\"]*)\")?\\s*" +
|
||||
"/>", java.util.regex.Pattern.CASE_INSENSITIVE
|
||||
java.util.regex.Pattern valueTagPattern = java.util.regex.Pattern.compile(
|
||||
"<Value\\s+([^>]+)/>",
|
||||
java.util.regex.Pattern.CASE_INSENSITIVE
|
||||
);
|
||||
|
||||
java.util.regex.Pattern attributePattern = java.util.regex.Pattern.compile(
|
||||
"(name|desc|type|DO|DA|BaseFlag|LimitUp|LimitDown|Coefficient|Offset|PltFlag)=\"([^\"]*)\"",
|
||||
java.util.regex.Pattern.CASE_INSENSITIVE
|
||||
);
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
@@ -441,14 +539,23 @@ public class JsonToXmlConversionService {
|
||||
continue;
|
||||
}
|
||||
|
||||
java.util.regex.Matcher matcher = valueRulePattern.matcher(line);
|
||||
if (matcher.find()) {
|
||||
java.util.regex.Matcher tagMatcher = valueTagPattern.matcher(line);
|
||||
if (tagMatcher.find()) {
|
||||
String attributesStr = tagMatcher.group(1);
|
||||
|
||||
java.util.Map<String, String> attrMap = new java.util.HashMap<>();
|
||||
java.util.regex.Matcher attrMatcher = attributePattern.matcher(attributesStr);
|
||||
while (attrMatcher.find()) {
|
||||
attrMap.put(attrMatcher.group(1).toLowerCase(), attrMatcher.group(2));
|
||||
}
|
||||
|
||||
RuleBasedXmlMappingService.ValueRule rule = new RuleBasedXmlMappingService.ValueRule();
|
||||
rule.setName(matcher.group(1));
|
||||
rule.setDesc(matcher.group(2));
|
||||
rule.setType(matcher.group(3));
|
||||
String doPath = matcher.group(4) != null ? matcher.group(4) : "";
|
||||
String daPath = matcher.group(5) != null ? matcher.group(5) : "";
|
||||
rule.setName(attrMap.getOrDefault("name", ""));
|
||||
rule.setDesc(attrMap.getOrDefault("desc", ""));
|
||||
rule.setType(attrMap.getOrDefault("type", ""));
|
||||
|
||||
String doPath = attrMap.getOrDefault("do", "");
|
||||
String daPath = attrMap.getOrDefault("da", "");
|
||||
|
||||
if (indexMapping != null && !doPath.isEmpty()) {
|
||||
doPath = applyIndexMapping(doPath, currentGroup, indexMapping, rule.getName(), rule.getDesc());
|
||||
@@ -456,13 +563,16 @@ public class JsonToXmlConversionService {
|
||||
|
||||
rule.setDoPath(doPath);
|
||||
rule.setDaPath(daPath);
|
||||
rule.setBaseFlag(matcher.group(6));
|
||||
rule.setLimitUp(matcher.group(7));
|
||||
rule.setLimitDown(matcher.group(8));
|
||||
rule.setCoefficient(matcher.group(9));
|
||||
rule.setBaseFlag(attrMap.getOrDefault("baseflag", null));
|
||||
rule.setLimitUp(attrMap.getOrDefault("limitup", null));
|
||||
rule.setLimitDown(attrMap.getOrDefault("limitdown", null));
|
||||
rule.setCoefficient(attrMap.getOrDefault("coefficient", null));
|
||||
rule.setOffset(attrMap.getOrDefault("offset", null));
|
||||
rule.setPltFlag(attrMap.getOrDefault("pltflag", null));
|
||||
|
||||
String key = rule.getDesc();
|
||||
if (!key.isEmpty() && hasValidDoOrDa(rule)) {
|
||||
|
||||
if (!key.isEmpty()) {//!key.isEmpty() && hasValidDoOrDa(rule)
|
||||
rules.computeIfAbsent(key.toLowerCase(), k -> new ArrayList<>()).add(rule);
|
||||
}
|
||||
}
|
||||
@@ -710,6 +820,8 @@ public class JsonToXmlConversionService {
|
||||
} else {
|
||||
sb.append("[").append(start).append("]");
|
||||
}
|
||||
}else{
|
||||
sb.append("$cVal");
|
||||
}
|
||||
}
|
||||
if (typeName != null && !typeName.isEmpty()) {
|
||||
@@ -770,9 +882,9 @@ public class JsonToXmlConversionService {
|
||||
boolean matched = false;
|
||||
|
||||
for (RuleBasedXmlMappingService.ValueRule rule : ruleVariants) {
|
||||
if (rule.getName() == null || rule.getDoPath().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
// if (rule.getName() == null || rule.getDoPath().isEmpty()) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
if(mappingMetrics.containsKey(rule.getName())){
|
||||
MetricInfo metric = mappingMetrics.get(rule.getName());
|
||||
@@ -909,30 +1021,32 @@ public class JsonToXmlConversionService {
|
||||
private String applyRulesToXml(String xmlContent,
|
||||
java.util.Map<String, RuleBasedXmlMappingService.ValueRule> applicableRules) {
|
||||
|
||||
java.util.regex.Pattern xmlValuePattern = java.util.regex.Pattern.compile(
|
||||
"<Value\\s+" +
|
||||
"name=\"([^\"]+)\"\\s+" +
|
||||
"desc=\"([^\"]+)\"\\s+" +
|
||||
"type=\"([^\"]+)\"\\s+" +
|
||||
"(?:DO=\"([^\"]*)\")?\\s*" +
|
||||
"(?:DA=\"([^\"]*)\")?\\s*" +
|
||||
"(?:BaseFlag=\"([^\"]*)\")?\\s*" +
|
||||
"(?:LimitUp=\"([^\"]*)\")?\\s*" +
|
||||
"(?:LimitDown=\"([^\"]*)\")?\\s*" +
|
||||
"(?:Coefficient=\"([^\"]*)\")?\\s*" +
|
||||
"/>", java.util.regex.Pattern.CASE_INSENSITIVE
|
||||
java.util.regex.Pattern valueTagPattern = java.util.regex.Pattern.compile(
|
||||
"<Value\\s+([^>]+)/>",
|
||||
java.util.regex.Pattern.CASE_INSENSITIVE
|
||||
);
|
||||
|
||||
java.util.regex.Matcher matcher = xmlValuePattern.matcher(xmlContent);
|
||||
java.util.regex.Pattern attributePattern = java.util.regex.Pattern.compile(
|
||||
"(name|desc|type|DO|DA|BaseFlag|LimitUp|LimitDown|Coefficient|Offset|PltFlag)=\"([^\"]*)\"",
|
||||
java.util.regex.Pattern.CASE_INSENSITIVE
|
||||
);
|
||||
|
||||
java.util.regex.Matcher matcher = valueTagPattern.matcher(xmlContent);
|
||||
StringBuffer result = new StringBuffer();
|
||||
|
||||
while (matcher.find()) {
|
||||
String name = matcher.group(1);
|
||||
String desc = matcher.group(2);
|
||||
String type = matcher.group(3);
|
||||
String attributesStr = matcher.group(1);
|
||||
|
||||
java.util.Map<String, String> attrMap = new java.util.HashMap<>();
|
||||
java.util.regex.Matcher attrMatcher = attributePattern.matcher(attributesStr);
|
||||
while (attrMatcher.find()) {
|
||||
attrMap.put(attrMatcher.group(1).toLowerCase(), attrMatcher.group(2));
|
||||
}
|
||||
|
||||
String name = attrMap.getOrDefault("name", "");
|
||||
String desc = attrMap.getOrDefault("desc", "");
|
||||
String type = attrMap.getOrDefault("type", "6");
|
||||
|
||||
// String key = buildRuleKey(name, desc);
|
||||
//RuleBasedXmlMappingService.ValueRule matchedRule = applicableRules.get(key.toLowerCase());
|
||||
RuleBasedXmlMappingService.ValueRule matchedRule = applicableRules.get(desc);
|
||||
if (matchedRule != null) {
|
||||
StringBuilder valueNode = new StringBuilder("<Value ");
|
||||
@@ -958,6 +1072,12 @@ public class JsonToXmlConversionService {
|
||||
if (matchedRule.getCoefficient() != null && !matchedRule.getCoefficient().isEmpty()) {
|
||||
valueNode.append("Coefficient=\"").append(escapeXml(matchedRule.getCoefficient())).append("\" ");
|
||||
}
|
||||
if (matchedRule.getOffset() != null && !matchedRule.getOffset().isEmpty()) {
|
||||
valueNode.append("Offset=\"").append(escapeXml(matchedRule.getOffset())).append("\" ");
|
||||
}
|
||||
if (matchedRule.getPltFlag() != null && !matchedRule.getPltFlag().isEmpty()) {
|
||||
valueNode.append("PltFlag=\"").append(escapeXml(matchedRule.getPltFlag())).append("\" ");
|
||||
}
|
||||
|
||||
valueNode.append("/>");
|
||||
matcher.appendReplacement(result, java.util.regex.Matcher.quoteReplacement(valueNode.toString()));
|
||||
@@ -986,7 +1106,7 @@ public class JsonToXmlConversionService {
|
||||
}
|
||||
methodDescribeList.add(message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 指标信息内部类
|
||||
*/
|
||||
|
||||
@@ -61,7 +61,7 @@ public class RuleBasedXmlMappingService {
|
||||
private static final Pattern DO_NO_INDEX_PATTERN = Pattern.compile("(MMXU|MHAI|MSQI|MFLK|QVVR)\\$");
|
||||
|
||||
public InputStream loadDefaultXmlFile( ) throws Exception {
|
||||
ClassPathResource templateResource = new ClassPathResource("template/JiangSu_Config2.xml");
|
||||
ClassPathResource templateResource = new ClassPathResource("template/JiangSu_Config1.xml");
|
||||
|
||||
if (!templateResource.exists()) {
|
||||
return null;
|
||||
@@ -808,6 +808,8 @@ public class RuleBasedXmlMappingService {
|
||||
private String limitUp;
|
||||
private String limitDown;
|
||||
private String coefficient;
|
||||
private String offset;
|
||||
private String pltFlag;
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
@@ -827,6 +829,10 @@ public class RuleBasedXmlMappingService {
|
||||
public void setLimitDown(String limitDown) { this.limitDown = limitDown; }
|
||||
public String getCoefficient() { return coefficient; }
|
||||
public void setCoefficient(String coefficient) { this.coefficient = coefficient; }
|
||||
public String getOffset() { return offset; }
|
||||
public void setOffset(String offset) { this.offset = offset; }
|
||||
public String getPltFlag() { return pltFlag; }
|
||||
public void setPltFlag(String pltFlag) { this.pltFlag = pltFlag; }
|
||||
}
|
||||
|
||||
static class IcdMetricInfo {
|
||||
|
||||
@@ -26,4 +26,12 @@ public class MappingModuleConfig {
|
||||
/** 默认输出目录。 */
|
||||
@Value("${icd.mapping.default-output-dir:}")
|
||||
private String defaultOutputDir;
|
||||
|
||||
/** JSON转XML使用的默认XML模板路径。 */
|
||||
@Value("${icd.mapping.json-to-xml-template-path:template/JiangSu_Config2.xml}")
|
||||
private String jsonToXmlTemplatePath;
|
||||
|
||||
/** JSON转XML使用的默认规则文件路径。 */
|
||||
@Value("${icd.mapping.json-to-xml-rule-path:template/默认规则.txt}")
|
||||
private String jsonToXmlRulePath;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,617 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--注:#7#代表通配符,用于通配数据类型 采用8421码,8-CP95、4-最小值、2-最大值、1-平均值-->
|
||||
<!-- %2,50%代表通配符,用于通配谐波数据,采用范围编码,第一个数字表示谐波起始号,第二个数字谐波结束号-->
|
||||
<!-- SEQ=后面的值采用8421码 8-T、4-C、2-B、1-A-->
|
||||
<!--注:send类型:0-不发送kafka、1-发送kafka-->
|
||||
<!--注:type类型:0-DataType、1-监测点、2-剔除标记、3-发生时刻,毫秒、4-数据链表、5-相位、6-值索引、9-实时SOE事件-->
|
||||
<!--注:nane:参数项、desc:参数描述、type:参数等级、DO:数据对象名、DA:数据属性名、Coefficient:数据系数、Offset:起始序号偏移量、PltFlag:长时闪变标志-->
|
||||
<!--注:角型接线时,相角为0,不再进行DO、DA路径值扩展-->
|
||||
<JSConfigTemplate version="2021-12-15" author="ww" SelectStat="JiangSu" SelectReal="Kafka Producer" desc="昆明长水机场增加谐波类数据配置">
|
||||
<!--注:暂态事件解析规则配置 Flag:0-不分相 1-分相 如果Flag=0 A,B,C配置成一样,如果Flag=1,A,B,C根据实际配置-->
|
||||
<WavePhasic Flag="1" A="QVVR1" B="QVVR2" C="QVVR3" />
|
||||
<!--注:暂态事件持续事件单位:0-毫秒 1-秒-->
|
||||
<UnitOfTime Unit="0" />
|
||||
<!--注:上送值的时间:UTC-UTC时间 beijing-北京时间-->
|
||||
<ValueOfTime Unit="utc" />
|
||||
<!--注:录波文件的时间:UTC-UTC时间 beijing-北京时间-->
|
||||
<ComtradeFile WaveTimeFlag="beijing" />
|
||||
<IED name="PQMonitor" desc="电能质量监测装置" />
|
||||
<LDevice Prefix="PQM" desc="监测点" />
|
||||
<ReportMap>
|
||||
<!--用于映射需要触发那些报告-->
|
||||
<!--ReportControl:ID,RCBName,intgPd,dchg,qchg,dupd,period,gi,issuffixed,seqNum,timeStamp,reasonCode,dataSet,dataRef,bufOvfl,entryID,configRef,segmentation,FlickerFlag-->
|
||||
<!--600/60秒,即10分钟/1分钟-->
|
||||
<!--0,0,0,1,0为周期触发报告-->
|
||||
<!--1,0,0,0,0为变位触发报告-->
|
||||
<ReportStat>
|
||||
<Report ReportControl="LLN0$BR$brcbFlickerData,60,1,0,0,0,0,yes,1,1,1,1,1,0,1,1,1,3,1" />
|
||||
<Report ReportControl="LLN0$BR$brcbStatisticData,60,1,0,0,0,0,yes,1,1,1,1,1,0,1,1,1,1,0" />
|
||||
</ReportStat>
|
||||
<ReportReal>
|
||||
<Report ReportControl="LLN0$RP$urcbRealData,3,0,0,0,1,0,yes,1,1,1,1,1,0,1,1,1,3,2" />
|
||||
</ReportReal>
|
||||
<ReportEvent>
|
||||
<Report ReportControl="LLN0$BR$brcbQVVR,60,1,0,0,0,0,yes,1,1,1,1,1,0,1,1,1,8,1,0"/>
|
||||
<Report ReportControl="LLN0$BR$brcbRDRE,60,1,0,0,0,0,yes,1,1,1,1,1,0,1,1,1,8,1,0"/>
|
||||
<Report ReportControl="LLN0$BR$brcbGGIO,60,1,0,0,0,0,yes,1,1,1,1,1,0,1,1,1,8,1,0"/>
|
||||
<Report ReportControl="LLN0$BR$brcbLPHD,60,1,0,0,0,0,yes,1,1,1,1,1,0,1,1,1,8,1,0"/>
|
||||
</ReportEvent>
|
||||
</ReportMap>
|
||||
<Topic name="HISDATA" desc="历史稳态数据">
|
||||
<DataType name="DATA_TYPE" value="02" desc="历史短时闪变数据" type="0">
|
||||
<Monitor name="MONITOR" desc="监测点" type="1">
|
||||
<Item name="FLAG" value="0" desc="剔除标记" type="2" />
|
||||
<Item name="TIME" desc="发生时刻" type="3" />
|
||||
<Item name="F_S" desc="短时闪变和波动" type="4" >
|
||||
<Sequence name="SEQ" value="7" desc="相别" type="5" >
|
||||
<Value name="PST" desc="短时闪变" type="6" DO="" DA="" />
|
||||
<Value name="FLUC" desc="电压波动幅度" type="6" DO="" DA="" />
|
||||
<Value name="FLUCF" desc="电压波动频度" type="6" DO="" DA="" />
|
||||
</Sequence>
|
||||
<Sequence name="SEQ" value="112" desc="相别" type="5" >
|
||||
<Value name="PST" desc="线电压短时闪变" type="6" DO="" DA="" />
|
||||
<Value name="FLUC" desc="线电压波动幅度" type="6" DO="" DA="" />
|
||||
<Value name="FLUCF" desc="线电压波动频度" type="6" DO="" DA="" />
|
||||
</Sequence>
|
||||
</Item>
|
||||
</Monitor>
|
||||
</DataType>
|
||||
<DataType name="DATA_TYPE" value="03" desc="历史长时闪变数据" type="0">
|
||||
<Monitor name="MONITOR" desc="监测点" type="1">
|
||||
<Item name="FLAG" value="0" desc="剔除标记" type="2" />
|
||||
<Item name="TIME" desc="发生时刻" type="3" />
|
||||
<Item name="F_L" desc="闪变" type="4" >
|
||||
<Sequence name="SEQ" value="7" desc="相别" type="5" >
|
||||
<Value name="PLT" desc="长时闪变" type="6" DO="" DA="" />
|
||||
</Sequence>
|
||||
<Sequence name="SEQ" value="112" desc="相别" type="5" >
|
||||
<Value name="PLT" desc="线电压长时闪变" type="6" DO="" DA="" />
|
||||
</Sequence>
|
||||
</Item>
|
||||
</Monitor>
|
||||
</DataType>
|
||||
<DataType name="DATA_TYPE" value="04" desc="历史暂态数据" type="0" >
|
||||
<Monitor name="MONITOR" desc="监测点" type="1" >
|
||||
<Item name="VOLTAGE" desc="暂态指标" type="4" >
|
||||
<Sequence name="SEQ" value="7" desc="相别" type="5">
|
||||
<Value name="MAG" desc="残余电压" type="6" DO="" DA="" />
|
||||
<Value name="DUR" desc="持续时间" type="6" DO="" DA="" />
|
||||
<!--Value name="SEQ" desc="相别" type="6" DO="" DA="" />
|
||||
<Value name="STARTTIME" desc="开始时间" type="6" DO="" DA="" />
|
||||
<Value name="ENDTIME" desc="结束时间" type="6" DO="" DA="" />
|
||||
<Value name="DISKIND" desc="暂降类型" type="6" DO="" DA="" />
|
||||
<Value name="WAVEFILE" desc="波形文件名称" type="6" DO="" DA="" /-->
|
||||
</Sequence>
|
||||
</Item>
|
||||
</Monitor>
|
||||
</DataType>
|
||||
<DataType name="DATA_TYPE" value="01" desc="历史稳态数据" type="0">
|
||||
<Monitor name="MONITOR" desc="监测点" type="1">
|
||||
<Item name="FLAG" value="0" desc="剔除标记" type="2" />
|
||||
<Item name="TIME" desc="发生时刻" type="3" />
|
||||
<Item name="V" desc="电压" type="4" >
|
||||
<!--电压V部分(A-C相)-->
|
||||
<Sequence name="SEQ" value="7" desc="相别(A-C相)" type="5">
|
||||
<!--注:Coefficient 值系数,转成kafka的时候乘以系数;BaseFlag 基础数据标志 0或没有-非基础数据 1-基础数据;LimitUp 数据合理上限,如果包含*%UN字符表示需要乘以电压等级电压,如果包含*%U字符表示需要乘以电压等级/1.732电压例如:220kV则相电压上限为1.5*220;LimitDown 数据合理下限 如果包含*%U字符表示需要乘以电压等级电压-->
|
||||
<Value name="G_DELTA_V" desc="电压偏差95值" type="6" DO="" DA="" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
<Value name="DELTA_V" desc="电压偏差平均值" type="6" DO="" DA="" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
<Value name="MAX_DELTA_V" desc="电压偏差最大值" type="6" DO="" DA="" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
<Value name="MIN_DELTA_V" desc="电压偏差最小值" type="6" DO="" DA="" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
<Value name="G_VRMS" desc="电压有效值95值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="VRMS" desc="电压有效值平均值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="MAX_VRMS" desc="电压有效值最大值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="MIN_VRMS" desc="电压有效值最小值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="G_VRMS_LVR" desc="线电压有效值95值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="VRMS_LVR" desc="线电压有效值平均值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="MAX_VRMS_LVR" desc="线电压有效值最大值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="MIN_VRMS_LVR" desc="线电压有效值最小值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="G_VTHD" desc="电压总谐波畸变率95值" type="6" DO="" DA="" />
|
||||
<Value name="VTHD" desc="电压总谐波畸变率平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_VTHD" desc="电压总谐波畸变率最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_VTHD" desc="电压总谐波畸变率最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_VFUND_ANGLE" desc="基波电压相角95值" type="6" DO="" DA="" />
|
||||
<Value name="VFUND_ANGLE" desc="基波电压相角平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_VFUND_ANGLE" desc="基波电压相角最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_VFUND_ANGLE" desc="基波电压相角最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_V1" desc="基波电压有效值95值" type="6" DO="" DA="" />
|
||||
<Value name="V1" desc="基波电压有效值平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_V1" desc="基波电压有效值最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_V1" desc="基波电压有效值最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_SV_%0,49%" desc="间谐波电压含有率(f25-2475)95值" type="6" DO="" DA="" />
|
||||
<Value name="SV_%0,49%" desc="间谐波电压含有率(f25-2475)平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_SV_%0,49%" desc="间谐波电压含有率(f25-2475)最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_SV_%0,49%" desc="间谐波电压含有率(f25-2475)最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_V%2,50%" desc="谐波电压含有率(2-50)95值" type="6" DO="" DA="" />
|
||||
<Value name="V%2,50%" desc="谐波电压含有率(2-50)平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_V%2,50%" desc="谐波电压含有率(2-50)最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_V%2,50%" desc="谐波电压含有率(2-50)最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_VA%2,50%" desc="谐波电压相角(2-50)95值" type="6" DO="" DA="" />
|
||||
<Value name="VA%2,50%" desc="谐波电压相角(2-50)平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_VA%2,50%" desc="谐波电压相角(2-50)最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_VA%2,50%" desc="谐波电压相角(2-50)最小值" type="6" DO="" DA="" />
|
||||
</Sequence>
|
||||
<!--电压V部分(A-C相)角型接线-->
|
||||
<Sequence name="SEQ" value="112" desc="相别(A-C相)" type="5">
|
||||
<!--注:Coefficient 值系数,转成kafka的时候乘以系数;BaseFlag 基础数据标志 0或没有-非基础数据 1-基础数据;LimitUp 数据合理上限,如果包含*%UN字符表示需要乘以电压等级电压,如果包含*%U字符表示需要乘以电压等级/1.732电压例如:220kV则相电压上限为1.5*220;LimitDown 数据合理下限 如果包含*%U字符表示需要乘以电压等级电压-->
|
||||
<Value name="G_DELTA_V" desc="线电压偏差95值" type="6" DO="" DA="" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
<Value name="DELTA_V" desc="线电压偏差平均值" type="6" DO="" DA="" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
<Value name="MAX_DELTA_V" desc="线电压偏差最大值" type="6" DO="" DA="" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
<Value name="MIN_DELTA_V" desc="线电压偏差最小值" type="6" DO="" DA="" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
<Value name="G_VRMS_LVR" desc="线电压有效值95值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="VRMS_LVR" desc="线电压有效值平均值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="MAX_VRMS_LVR" desc="线电压有效值最大值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="MIN_VRMS_LVR" desc="线电压有效值最小值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="G_VTHD" desc="线电压总谐波畸变率95值" type="6" DO="" DA="" />
|
||||
<Value name="VTHD" desc="线电压总谐波畸变率平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_VTHD" desc="线电压总谐波畸变率最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_VTHD" desc="线电压总谐波畸变率最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_VFUND_ANGLE" desc="基波线电压相角95值" type="6" DO="" DA="" />
|
||||
<Value name="VFUND_ANGLE" desc="基波线电压相角平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_VFUND_ANGLE" desc="基波线电压相角最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_VFUND_ANGLE" desc="基波线电压相角最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_V1" desc="基波线电压有效值95值" type="6" DO="" DA="" />
|
||||
<Value name="V1" desc="基波线电压有效值平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_V1" desc="基波线电压有效值最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_V1" desc="基波线电压有效值最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_SV_%0,49%" desc="间谐波线电压含有率(f25-2475)95值" type="6" DO="" DA="" />
|
||||
<Value name="SV_%0,49%" desc="间谐波线电压含有率(f25-2475)平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_SV_%0,49%" desc="间谐波线电压含有率(f25-2475)最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_SV_%0,49%" desc="间谐波线电压含有率(f25-2475)最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_V%2,50%" desc="谐波线电压含有率(2-50)95值" type="6" DO="" DA="" />
|
||||
<Value name="V%2,50%" desc="谐波线电压含有率(2-50)平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_V%2,50%" desc="谐波线电压含有率(2-50)最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_V%2,50%" desc="谐波线电压含有率(2-50)最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_VA%2,50%" desc="谐波线电压相角(2-50)95值" type="6" DO="" DA="" />
|
||||
<Value name="VA%2,50%" desc="谐波线电压相角(2-50)平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_VA%2,50%" desc="谐波线电压相角(2-50)最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_VA%2,50%" desc="谐波线电压相角(2-50)最小值" type="6" DO="" DA="" />
|
||||
</Sequence>
|
||||
<Sequence name="SEQ" value="8" desc="相别(T相)" type="5">
|
||||
<!--电压V部分(T相)-->
|
||||
<Value name="G_VNSEQ" desc="负序电压95值" type="6" DO="" DA="" />
|
||||
<Value name="VNSEQ" desc="负序电压平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_VNSEQ" desc="负序电压最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_VNSEQ" desc="负序电压最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_V_UNBAN" desc="负序电压不平衡95值" type="6" DO="" DA="" />
|
||||
<Value name="V_UNBAN" desc="负序电压不平衡平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_V_UNBAN" desc="负序电压不平衡最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_V_UNBAN" desc="负序电压不平衡最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_VZSEQ" desc="零序电压95值" type="6" DO="" DA="" />
|
||||
<Value name="VZSEQ" desc="零序电压平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_VZSEQ" desc="零序电压最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_VZSEQ" desc="零序电压最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_VZSEQ_UNBAN" desc="零序电压不平衡95值" type="6" DO="" DA="" />
|
||||
<Value name="VZSEQ_UNBAN" desc="零序电压不平衡平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_VZSEQ_UNBAN" desc="零序电压不平衡最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_VZSEQ_UNBAN" desc="零序电压不平衡最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_FREQ" desc="频率95值" type="6" DO="" DA="" />
|
||||
<Value name="FREQ" desc="频率平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_FREQ" desc="频率最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_FREQ" desc="频率最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_DELTA_FREQ" desc="频率偏差95值" type="6" DO="" DA="" />
|
||||
<Value name="DELTA_FREQ" desc="频率偏差平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_DELTA_FREQ" desc="频率偏差最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_DELTA_FREQ" desc="频率偏差最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_VPSEQ" desc="正序电压95值" type="6" DO="" DA="" />
|
||||
<Value name="VPSEQ" desc="正序电压平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_VPSEQ" desc="正序电压最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_VPSEQ" desc="正序电压最小值" type="6" DO="" DA="" />
|
||||
</Sequence>
|
||||
</Item>
|
||||
<Item name="I" desc="电流" type="4" >
|
||||
<!--电流I部分(A-C相)-->
|
||||
<Sequence name="SEQ" value="7" desc="相别(A-C相)" type="5">
|
||||
<Value name="G_IRMS" desc="电流有效值95值" type="6" DO="" DA="" />
|
||||
<Value name="IRMS" desc="电流有效值平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_IRMS" desc="电流有效值最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_IRMS" desc="电流有效值最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_I1" desc="基波电流有效值95值" type="6" DO="" DA="" />
|
||||
<Value name="I1" desc="基波电流有效值平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_I1" desc="基波电流有效值最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_I1" desc="基波电流有效值最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_IFUND_ANGLE" desc="基波电流相角95值" type="6" DO="" DA="" />
|
||||
<Value name="IFUND_ANGLE" desc="基波电流相角平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_IFUND_ANGLE" desc="基波电流相角最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_IFUND_ANGLE" desc="基波电流相角最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_SI_%0,49%" desc="间谐波电流幅值(f25-2475)95值" type="6" DO="" DA="" />
|
||||
<Value name="SI_%0,49%" desc="间谐波电流幅值(f25-2475)平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_SI_%0,49%" desc="间谐波电流幅值(f25-2475)最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_SI_%0,49%" desc="间谐波电流幅值(f25-2475)最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_I%1,50%" desc="谐波电流幅值(2-50)95值" type="6" DO="" DA="" />
|
||||
<Value name="I%1,50%" desc="谐波电流幅值(2-50)平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_I%1,50%" desc="谐波电流幅值(2-50)最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_I%1,50%" desc="谐波电流幅值(2-50)最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_IA%1,50%" desc="谐波电流相角(2-50)95值" type="6" DO="" DA="" />
|
||||
<Value name="IA%1,50%" desc="谐波电流相角(2-50)平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_IA%1,50%" desc="谐波电流相角(2-50)最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_IA%1,50%" desc="谐波电流相角(2-50)最小值" type="6" DO="" DA="" />
|
||||
<Value name="ITHD" desc="电流总谐波畸变率平均值" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="MAX_ITHD" desc="电流总谐波畸变率最大值" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="MIN_ITHD" desc="电流总谐波畸变率最小值" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="G_ITHD" desc="电流总谐波畸变率95值" type="6" DO="" DA="" Coefficient="1" />
|
||||
</Sequence>
|
||||
<!--电流I部分(T相)-->
|
||||
<Sequence name="SEQ" value="8" desc="相别(T相)" type="5">
|
||||
<Value name="G_INSEQ" desc="负序电流95值" type="6" DO="" DA="" />
|
||||
<Value name="INSEQ" desc="负序电流平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_INSEQ" desc="负序电流最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_INSEQ" desc="负序电流最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_I_UNBAN" desc="负序电流不平衡95值" type="6" DO="" DA="" />
|
||||
<Value name="I_UNBAN" desc="负序电流不平衡平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_I_UNBAN" desc="负序电流不平衡最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_I_UNBAN" desc="负序电流不平衡最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_IZSEQ" desc="零序电流95值" type="6" DO="" DA="" />
|
||||
<Value name="IZSEQ" desc="零序电流平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_IZSEQ" desc="零序电流最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_IZSEQ" desc="零序电流最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_VZSEQ_UNBAN" desc="零序电流不平衡95值" type="6" DO="" DA="" />
|
||||
<Value name="VZSEQ_UNBAN" desc="零序电流不平衡平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_IZSEQ_UNBAN" desc="零序电流不平衡最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_IZSEQ_UNBAN" desc="零序电流不平衡最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_IPSEQ" desc="正序电流95值" type="6" DO="" DA="" />
|
||||
<Value name="IPSEQ" desc="正序电流平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_IPSEQ" desc="正序电流最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_IPSEQ" desc="正序电流最小值" type="6" DO="" DA="" />
|
||||
</Sequence>
|
||||
</Item>
|
||||
<Item name="PQ" desc="功率" type="4" >
|
||||
<!--功率PQ部分(A-C相)-->
|
||||
<Sequence name="SEQ" value="7" desc="相别(A-C相)" type="5">
|
||||
<Value name="G_PF" desc="功率因数95值" type="6" DO="" DA="" />
|
||||
<Value name="PF" desc="功率因数平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_PF" desc="功率因数最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_PF" desc="功率因数最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_DF" desc="位移功率因数95值" type="6" DO="" DA="" />
|
||||
<Value name="DF" desc="位移功率因数平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_DF" desc="位移功率因数最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_DF" desc="位移功率因数最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_P_FUND" desc="基波有功功率95值" type="6" DO="" DA="" />
|
||||
<Value name="P_FUND" desc="基波有功功率平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_P_FUND" desc="基波有功功率最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_P_FUND" desc="基波有功功率最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_S" desc="视在功率95值" type="6" DO="" DA="" />
|
||||
<Value name="S" desc="视在功率平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_S" desc="视在功率最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_S" desc="视在功率最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_Q" desc="无功功率95值" type="6" DO="" DA="" />
|
||||
<Value name="Q" desc="无功功率平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_Q" desc="无功功率最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_Q" desc="无功功率最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_P%1,50%" desc="谐波有功功率(2-50)95值" type="6" DO="" DA="" />
|
||||
<Value name="P%1,50%" desc="谐波有功功率(2-50)平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_P%1,50%" desc="谐波有功功率(2-50)最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_P%1,50%" desc="谐波有功功率(2-50)最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_Q%1,50%" desc="谐波无功功率(2-50)95值" type="6" DO="" DA="" />
|
||||
<Value name="Q%1,50%" desc="谐波无功功率(2-50)平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_Q%1,50%" desc="谐波无功功率(2-50)最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_Q%1,50%" desc="谐波无功功率(2-50)最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_S%1,50%" desc="谐波视在功率(2-50)95值" type="6" DO="" DA="" />
|
||||
<Value name="S%1,50%" desc="谐波视在功率(2-50)平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_S%1,50%" desc="谐波视在功率(2-50)最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_S%1,50%" desc="谐波视在功率(2-50)最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_P" desc="有功功率95值" type="6" DO="" DA="" />
|
||||
<Value name="P" desc="有功功率平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_P" desc="有功功率最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_P" desc="有功功率最小值" type="6" DO="" DA="" />
|
||||
</Sequence>
|
||||
<!--功率PQ部分(T相)-->
|
||||
<Sequence name="SEQ" value="8" desc="相别(T相)" type="5">
|
||||
<Value name="G_PF" desc="总功率因数95值" type="6" DO="" DA="" />
|
||||
<Value name="PF" desc="总功率因数平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_PF" desc="总功率因数最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_PF" desc="总功率因数最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_DF" desc="总位移功率因数95值" type="6" DO="" DA="" />
|
||||
<Value name="DF" desc="总位移功率因数平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_DF" desc="总位移功率因数最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_DF" desc="总位移功率因数最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_S" desc="总视在功率95值" type="6" DO="" DA="" />
|
||||
<Value name="S" desc="总视在功率平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_S" desc="总视在功率最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_S" desc="总视在功率最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_Q" desc="总无功功率95值" type="6" DO="" DA="" />
|
||||
<Value name="Q" desc="总无功功率平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_Q" desc="总无功功率最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_Q" desc="总无功功率最小值" type="6" DO="" DA="" />
|
||||
<Value name="G_P" desc="总有功功率95值" type="6" DO="" DA="" />
|
||||
<Value name="P" desc="总有功功率平均值" type="6" DO="" DA="" />
|
||||
<Value name="MAX_P" desc="总有功功率最大值" type="6" DO="" DA="" />
|
||||
<Value name="MIN_P" desc="总有功功率最小值" type="6" DO="" DA="" />
|
||||
</Sequence>
|
||||
</Item>
|
||||
</Monitor>
|
||||
</DataType>
|
||||
</Topic>
|
||||
<Topic name="RTDATA" desc="实时数据">
|
||||
<DataType name="DATA_TYPE" value="01" desc="实时稳态数据" send="1" type="0">
|
||||
<Monitor name="-" desc="监测点" type="1">
|
||||
<Item name="FLAG" value="0" desc="剔除标记" type="3" />
|
||||
<Item name="TIME" desc="发生时刻" type="3" />
|
||||
<Item name="V" desc="电压" type="4" >
|
||||
<!--电压V部分(A-C相)-->
|
||||
<Sequence name="SEQ" value="7" desc="相别(A-C相)" type="5">
|
||||
<Value name="VRMS" desc="实时电压有效值" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="DELTA_V" desc="实时电压偏差" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="VTHD" desc="实时电压总谐波畸变率" type="6" DO="" DA="" Coefficient="1" />
|
||||
<!--icd101/102模型-->
|
||||
<Value name="VFUND_ANGLE" desc="实时基波电压相角" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="VRMS_LVR" desc="实时线电压有效值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<!--icd100模型-->
|
||||
<!--<Value name="V_ANGLE" desc="基波电压相角" type="6" DO="" DA="" Coefficient="1" /-->
|
||||
<!--icd101/102模型-->
|
||||
<Value name="V1" desc="实时基波电压有效值" type="6" DO="" DA="" Coefficient="1" />
|
||||
<!--icd100模型-->
|
||||
<!--Value name="V1" desc="基波电压有效值" type="6" DO="" DA="" Coefficient="1" /-->
|
||||
<Value name="V%2,50%" desc="实时2-50次谐波电压含有率" type="6" DO="" DA="" Coefficient="1" Offset="0" />
|
||||
<Value name="SV_%0,49%" desc="实时1-50次间谐波电压含有率" type="6" DO="" DA="" Coefficient="1" Offset="0" />
|
||||
<Value name="VA%2,50%" desc="实时谐波电压相角(2-50)" type="6" DO="" DA="" />
|
||||
</Sequence>
|
||||
<!--电压V部分(A-C相)角型接线-->
|
||||
<Sequence name="SEQ" value="112" desc="相别(A-C相)" type="5">
|
||||
<!--注:Coefficient 值系数,转成kafka的时候乘以系数;BaseFlag 基础数据标志 0或没有-非基础数据 1-基础数据;LimitUp 数据合理上限,如果包含*%UN字符表示需要乘以电压等级电压,如果包含*%U字符表示需要乘以电压等级/1.732电压例如:220kV则相电压上限为1.5*220;LimitDown 数据合理下限 如果包含*%U字符表示需要乘以电压等级电压-->
|
||||
<Value name="DELTA_V" desc="实时线电压偏差" type="6" DO="" DA="" Coefficient="1"/>
|
||||
<Value name="VRMS_LVR" desc="实时线电压有效值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" Coefficient="1" />
|
||||
<Value name="VTHD" desc="实时线电压总谐波畸变率" type="6" DO="" DA="" Coefficient="1"/>
|
||||
<Value name="VFUND_ANGLE" desc="实时基波线电压相角" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="V1" desc="实时基波线电压有效值" type="6" DO="" DA="" Coefficient="1"/>
|
||||
<Value name="SV_%0,49%" desc="实时间谐波线电压含有率(f25-2475)" type="6" DO="" DA="" Coefficient="1" Offset="0" />
|
||||
<Value name="V%2,50%" desc="实时谐波线电压含有率(2-50)" type="6" DO="" DA="" Coefficient="1" Offset="0" />
|
||||
<Value name="VA%2,50%" desc="实时谐波线电压相角(2-50)" type="6" DO="" DA="" />
|
||||
</Sequence>
|
||||
<Sequence name="SEQ" value="8" desc="相别(T相)" type="5">
|
||||
<!--电压V部分(T相)-->
|
||||
<Value name="VNSEQ" desc="实时负序电压" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="V_UNBAN" desc="实时负序电压不平衡" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="VZSEQ" desc="实时零序电压" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="VZSEQ_UNBAN" desc="实时零序电压不平衡" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="FREQ" desc="实时频率" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="DELTA_FREQ" desc="实时频率偏差" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="VPSEQ" desc="实时正序电压" type="6" DO="" DA="" Coefficient="1" />
|
||||
</Sequence>
|
||||
</Item>
|
||||
<Item name="I" desc="电流" type="4" >
|
||||
<!--电流I部分(A-C相)-->
|
||||
<Sequence name="SEQ" value="7" desc="相别(A-C相)" type="5">
|
||||
<Value name="IRMS" desc="实时电流有效值" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="I1" desc="实时基波电流有效值" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="ITHD" desc="实时电流总谐波畸变率" type="6" DO="" DA="" Coefficient="1" />
|
||||
<!--icd101/102模型-->
|
||||
<Value name="I_ANGLE" desc="实时基波电流相角" type="6" DO="" DA="" Coefficient="1" />
|
||||
<!--icd100模型-->
|
||||
<!--Value name="I_ANGLE" desc="基波电流相角" type="6" DO="" DA="" Coefficient="1" /-->
|
||||
<Value name="I%2,50%" desc="实时2-50次谐波电流幅值" type="6" DO="" DA="" Coefficient="1" Offset="0" />
|
||||
<Value name="SI_%0,49%" desc="实时1-50次间谐波电流含有率" type="6" DO="" DA="" Coefficient="1" Offset="0" />
|
||||
<!--Value name="I%1,49%" desc="1-50次谐波电流含有率" type="6" DO="" DA="" Coefficient="1" Offset="1" /-->
|
||||
</Sequence>
|
||||
<!--电流I部分(T相)-->
|
||||
<Sequence name="SEQ" value="8" desc="相别(T相)" type="5">
|
||||
<Value name="INSEQ" desc="实时负序电流" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="I_UNBAN" desc="实时负序电流不平衡" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="IZSEQ" desc="实时零序电流" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="IZSEQ_UNBAN" desc="实时零序电流不平衡" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="IPSEQ" desc="实时正序电流" type="6" DO="" DA="" Coefficient="1" />
|
||||
</Sequence>
|
||||
</Item>
|
||||
<Item name="PQ" desc="功率" type="4" >
|
||||
<!--功率PQ部分(A-C相)-->
|
||||
<Sequence name="SEQ" value="7" desc="相别(A-C相)" type="5">
|
||||
<Value name="PF" desc="实时功率因数" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="DF" desc="实时基波功率因数" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="P_FUND" desc="实时基波有功功率" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="S" desc="实时视在功率" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="Q" desc="实时无功功率" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="P" desc="实时有功功率" type="6" DO="" DA="" Coefficient="1" />
|
||||
</Sequence>
|
||||
<!--功率PQ部分(T相)-->
|
||||
<Sequence name="SEQ" value="8" desc="相别(T相)" type="5">
|
||||
<Value name="PF" desc="实时总功率因数" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="DF" desc="实时总基波功率因数" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="S" desc="实时总视在功率" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="Q" desc="实时总无功功率" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="P" desc="实时总有功功率" type="6" DO="" DA="" Coefficient="1" />
|
||||
</Sequence>
|
||||
</Item>
|
||||
</Monitor>
|
||||
</DataType>
|
||||
<DataType name="DATA_TYPE" value="02" desc="实时闪变数据" send="0" type="0">
|
||||
<Monitor name="MONITOR" desc="监测点" type="1">
|
||||
<Item name="FLAG" value="0" desc="剔除标记" type="3" />
|
||||
<Item name="TIME" desc="发生时刻" type="3" />
|
||||
<Item name="F_S" desc="短闪变" type="4" >
|
||||
<Sequence name="SEQ" value="7" desc="相别" type="5" >
|
||||
<Value name="SF" desc="实时短时闪变" type="6" DO="" DA="" Coefficient="1" PltFlag="False" />
|
||||
</Sequence>
|
||||
<Sequence name="SEQ" value="112" desc="相别" type="5" >
|
||||
<Value name="SF" desc="实时线电压短时闪变" type="6" DO="" DA="" Coefficient="1" PltFlag="False" />
|
||||
</Sequence>
|
||||
</Item>
|
||||
</Monitor>
|
||||
</DataType>
|
||||
<DataType name="DATA_TYPE" value="03" desc="实时闪变数据" send="0" type="0">
|
||||
<Monitor name="MONITOR" desc="监测点" type="1">
|
||||
<Item name="FLAG" value="0" desc="剔除标记" type="3" />
|
||||
<Item name="TIME" desc="发生时刻" type="3" />
|
||||
<Item name="F_L" desc="长闪变" type="4" >
|
||||
<Sequence name="SEQ" value="7" desc="相别" type="5" >
|
||||
<Value name="LF" desc="实时长时闪变" type="6" DO="" DA="" Coefficient="1" PltFlag="True" />
|
||||
</Sequence>
|
||||
<Sequence name="SEQ" value="112" desc="相别" type="5" >
|
||||
<Value name="LF" desc="实时线电压长时闪变" type="6" DO="" DA="" Coefficient="1" PltFlag="True" />
|
||||
</Sequence>
|
||||
</Item>
|
||||
</Monitor>
|
||||
</DataType>
|
||||
<DataType name="DATA_TYPE" value="04" desc="实时暂态事件" send="0" type="0" >
|
||||
<Monitor name="MONITOR" desc="监测点" type="1" >
|
||||
<Item name="VOLTAGE" desc="暂态指标" type="4" >
|
||||
<Sequence name="SEQ" value="-" desc="相别" type="5">
|
||||
<Value name="MAG" desc="实时残余电压" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="DUR" desc="实时持续时间" type="6" DO="" DA="" Coefficient="1" />
|
||||
<!--Value name="STARTTIME" desc="开始时间" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="ENDTIME" desc="结束时间" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="DISKIND" desc="暂降类型" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="WAVEFILE" desc="波形文件名称" type="6" DO="" DA="" Coefficient="1" /-->
|
||||
</Sequence>
|
||||
</Item>
|
||||
</Monitor>
|
||||
</DataType>
|
||||
</Topic>
|
||||
<Topic name="RTDATASOE" desc="实时SOE事件">
|
||||
<DataType name="DATA_TYPE" value="01" desc="稳态SOE事件" type="0" >
|
||||
<SOE desc="频率偏差越上限" type="9" DO="" DA="" TriggerFlag="Alm1" />
|
||||
<SOE desc="频率偏差越下限" type="9" DO="" DA="" TriggerFlag="Alm2" />
|
||||
<SOE desc="电压偏差越上限" type="9" DO="" DA="" TriggerFlag="Alm3" />
|
||||
<SOE desc="电压偏差越下限" type="9" DO="" DA="" TriggerFlag="Alm4" />
|
||||
<SOE desc="短时闪变越限" type="9" DO="" DA="" TriggerFlag="Alm5" />
|
||||
<SOE desc="长时闪变越限" type="9" DO="" DA="" TriggerFlag="Alm6" />
|
||||
<SOE desc="电压波动越限" type="9" DO="" DA="" TriggerFlag="Alm7" />
|
||||
<SOE desc="电压负序不平衡度越限" type="9" DO="" DA="" TriggerFlag="Alm8" />
|
||||
<SOE desc="电压总谐波畸变率越限" type="9" DO="" DA="" TriggerFlag="Alm9" />
|
||||
<SOE desc="2次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm10" />
|
||||
<SOE desc="3次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm11" />
|
||||
<SOE desc="4次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm12" />
|
||||
<SOE desc="5次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm13" />
|
||||
<SOE desc="6次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm14" />
|
||||
<SOE desc="7次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm15" />
|
||||
<SOE desc="8次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm16" />
|
||||
<SOE desc="9次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm17" />
|
||||
<SOE desc="10次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm18" />
|
||||
<SOE desc="11次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm19" />
|
||||
<SOE desc="12次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm20" />
|
||||
<SOE desc="13次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm21" />
|
||||
<SOE desc="14次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm22" />
|
||||
<SOE desc="15次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm23" />
|
||||
<SOE desc="16次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm24" />
|
||||
<SOE desc="17次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm25" />
|
||||
<SOE desc="18次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm26" />
|
||||
<SOE desc="19次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm27" />
|
||||
<SOE desc="20次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm28" />
|
||||
<SOE desc="21次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm29" />
|
||||
<SOE desc="22次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm30" />
|
||||
<SOE desc="23次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm31" />
|
||||
<SOE desc="24次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm32" />
|
||||
<SOE desc="25次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm33" />
|
||||
<SOE desc="26次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm34" />
|
||||
<SOE desc="27次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm35" />
|
||||
<SOE desc="28次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm36" />
|
||||
<SOE desc="29次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm37" />
|
||||
<SOE desc="30次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm38" />
|
||||
<SOE desc="31次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm39" />
|
||||
<SOE desc="32次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm40" />
|
||||
<SOE desc="33次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm41" />
|
||||
<SOE desc="34次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm42" />
|
||||
<SOE desc="35次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm43" />
|
||||
<SOE desc="36次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm44" />
|
||||
<SOE desc="37次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm45" />
|
||||
<SOE desc="38次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm46" />
|
||||
<SOE desc="39次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm47" />
|
||||
<SOE desc="40次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm48" />
|
||||
<SOE desc="41次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm49" />
|
||||
<SOE desc="42次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm50" />
|
||||
<SOE desc="43次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm51" />
|
||||
<SOE desc="44次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm52" />
|
||||
<SOE desc="45次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm53" />
|
||||
<SOE desc="46次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm54" />
|
||||
<SOE desc="47次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm55" />
|
||||
<SOE desc="48次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm56" />
|
||||
<SOE desc="49次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm57" />
|
||||
<SOE desc="50次谐波电压越限" type="9" DO="" DA="" TriggerFlag="Alm58" />
|
||||
<SOE desc="2次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm59" />
|
||||
<SOE desc="3次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm60" />
|
||||
<SOE desc="4次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm61" />
|
||||
<SOE desc="5次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm62" />
|
||||
<SOE desc="6次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm63" />
|
||||
<SOE desc="7次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm64" />
|
||||
<SOE desc="8次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm65" />
|
||||
<SOE desc="9次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm66" />
|
||||
<SOE desc="10次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm67" />
|
||||
<SOE desc="11次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm68" />
|
||||
<SOE desc="12次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm69" />
|
||||
<SOE desc="13次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm70" />
|
||||
<SOE desc="14次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm71" />
|
||||
<SOE desc="15次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm72" />
|
||||
<SOE desc="16次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm73" />
|
||||
<SOE desc="17次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm74" />
|
||||
<SOE desc="18次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm75" />
|
||||
<SOE desc="19次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm76" />
|
||||
<SOE desc="20次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm77" />
|
||||
<SOE desc="21次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm78" />
|
||||
<SOE desc="22次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm79" />
|
||||
<SOE desc="23次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm80" />
|
||||
<SOE desc="24次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm81" />
|
||||
<SOE desc="25次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm82" />
|
||||
<SOE desc="26次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm83" />
|
||||
<SOE desc="27次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm84" />
|
||||
<SOE desc="28次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm85" />
|
||||
<SOE desc="29次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm86" />
|
||||
<SOE desc="30次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm87" />
|
||||
<SOE desc="31次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm88" />
|
||||
<SOE desc="32次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm89" />
|
||||
<SOE desc="33次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm90" />
|
||||
<SOE desc="34次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm91" />
|
||||
<SOE desc="35次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm92" />
|
||||
<SOE desc="36次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm93" />
|
||||
<SOE desc="37次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm94" />
|
||||
<SOE desc="38次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm95" />
|
||||
<SOE desc="39次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm96" />
|
||||
<SOE desc="40次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm97" />
|
||||
<SOE desc="41次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm98" />
|
||||
<SOE desc="42次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm99" />
|
||||
<SOE desc="43次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm100" />
|
||||
<SOE desc="44次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm101" />
|
||||
<SOE desc="45次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm102" />
|
||||
<SOE desc="46次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm103" />
|
||||
<SOE desc="47次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm104" />
|
||||
<SOE desc="48次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm105" />
|
||||
<SOE desc="49次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm106" />
|
||||
<SOE desc="50次谐波电流越限" type="9" DO="" DA="" TriggerFlag="Alm107" />
|
||||
<SOE desc="电压越上限(自定义触发)" type="9" DO="" DA="" TriggerFlag="Alm108" />
|
||||
<SOE desc="电压越下限(自定义触发)" type="9" DO="" DA="" TriggerFlag="Alm109" />
|
||||
<SOE desc="电流越上限(自定义触发)" type="9" DO="" DA="" TriggerFlag="Alm110" />
|
||||
<SOE desc="电流越下限(自定义触发)" type="9" DO="" DA="" TriggerFlag="Alm111" />
|
||||
<SOE desc="电压变化量越限(自定义触发)" type="9" DO="" DA="" TriggerFlag="Alm112" />
|
||||
<SOE desc="电流变化量越限(自定义触发)" type="9" DO="" DA="" TriggerFlag="Alm113" />
|
||||
</DataType>
|
||||
<DataType name="DATA_TYPE" value="02" desc="暂态SOE事件" type="0" >
|
||||
</DataType>
|
||||
<DataType name="DATA_TYPE" value="03" desc="状态SOE事件" type="0" >
|
||||
<SOE desc="终端工作电源上电" type="9" DO="" DA="" TriggerFlag="PwrUp" />
|
||||
<SOE desc="终端工作电源掉电" type="9" DO="" DA="" TriggerFlag="PwrDn" />
|
||||
</DataType>
|
||||
</Topic>
|
||||
<Topic name="SOEDATA" desc="告警SOE">
|
||||
<SOE name="ThdVVal" desc="电压总畸变率越限告警" type="9" DO="" DA="" />
|
||||
<SOE name="ThdAVal" desc="电流总畸变率越限告警" type="9" DO="" DA="" />
|
||||
<SOE name="HToddVVal" desc="奇次谐波电压含有率越限告警" type="9" DO="" DA="" />
|
||||
<SOE name="HTeddVVal" desc="偶次谐波电压含有率越限告警" type="9" DO="" DA="" />
|
||||
<SOE name="H2AVal" desc="2次谐波电流越限告警" type="9" DO="" DA="" />
|
||||
<SOE name="H3AVal" desc="3次谐波电流越限告警" type="9" DO="" DA="" />
|
||||
<SOE name="H4AVal" desc="4次谐波电流越限告警" type="9" DO="" DA="" />
|
||||
<SOE name="H5AVal" desc="5次谐波电流越限告警" type="9" DO="" DA="" />
|
||||
<SOE name="H7AVal" desc="7次谐波电流越限告警" type="9" DO="" DA="" />
|
||||
<SOE name="H9AVal" desc="9次谐波电流越限告警" type="9" DO="" DA="" />
|
||||
<SOE name="H11AVal" desc="11次谐波电流越限告警" type="9" DO="" DA="" />
|
||||
<SOE name="H13AVal" desc="13次谐波电流越限告警" type="9" DO="" DA="" />
|
||||
|
||||
<SOE name="ImbNgVFVal" desc="电压负序不平衡度越限告警" type="9" DO="" DA="" />
|
||||
<SOE name="ImbNgAFVal" desc="电流负序不平衡度越限告警" type="9" DO="" DA="" />
|
||||
|
||||
<SOE name="OvHzStrVal" desc="频率高越限告警" type="9" DO="" DA="" />
|
||||
<SOE name="UnHzStrVal" desc="频率低越限告警" type="9" DO="" DA="" />
|
||||
|
||||
<SOE name="LRVInterrup" desc="长期电压中断告警" type="9" DO="" DA="" />
|
||||
|
||||
<SOE name="LRVSwell" desc="电压上偏差越限告警" type="9" DO="" DA="" />
|
||||
<SOE name="LRVSag" desc="电压下偏差越限告警" type="9" DO="" DA="" />
|
||||
<SOE name="PhPstVal" desc="短时闪变越限告警" type="9" DO="" DA="" />
|
||||
<SOE name="PhPltVal" desc="长时闪变越限告警" type="9" DO="" DA="" />
|
||||
|
||||
<SOE name="PhyStateFault" desc="终端运行状态:故障" type="9" DO="" DA="" />
|
||||
<SOE name="PhyStateRun" desc="终端运行状态:运行" type="9" DO="" DA="" />
|
||||
|
||||
<SOE name="PwrUp" desc="终端上电" type="9" DO="" DA="" />
|
||||
<SOE name="PwrDn" desc="终端掉电" type="9" DO="" DA="" />
|
||||
|
||||
<SOE name="CommInterrupt" desc="终端通信中断" type="9" DO="" DA="" />
|
||||
<SOE name="CommResume" desc="终端通信恢复" type="9" DO="" DA="" />
|
||||
</Topic>
|
||||
</JSConfigTemplate>
|
||||
@@ -1,127 +1,328 @@
|
||||
<Value name="相电压短时闪变值波动闪变值值" desc="短时闪变" type="6" DO="MFLK1$MX$PhPst" DA="phs*$cVal$mag$f" />
|
||||
<Value name="相电压电压变动幅值波动闪变值值" desc="电压波动幅值" type="6" DO="MMXU2$MX$FLUC" DA="phs*$cVal$mag$f" />
|
||||
<Value name="相电压电压变动频度波动闪变值值" desc="电压波动频度" type="6" DO="MMXU2$MX$FLUCCF" DA="phs*$cVal$mag$f" />
|
||||
<Value name="相电压长时闪变值波动闪变值值" desc="长时闪变" type="6" DO="MFLK2$MX$PhPlt" DA="phs*$cVal$mag$f" />
|
||||
<Value name="电压扰动事件特征幅值值" desc="残余电压" type="6" DO="QVVR0$MX$VVa" DA="mag$f" />
|
||||
<Value name="电压扰动事件持续时间" desc="持续时间" type="6" DO="QVVR0$MX$VVaTm" DA="mag$f" />
|
||||
<Value name="SEQ" desc="相别" type="6" DO="" DA="" />
|
||||
<Value name="STARTTIME" desc="开始时间" type="6" DO="" DA="" />
|
||||
<Value name="ENDTIME" desc="结束时间" type="6" DO="" DA="" />
|
||||
<Value name="DISKIND" desc="暂降类型" type="6" DO="" DA="" />
|
||||
<Value name="WAVEFILE" desc="波形文件名称" type="6" DO="" DA="" />
|
||||
<Value name="相电压偏差95值值" desc="电压偏差95值" type="6" DO="MMXU5$MX$VolDev" DA="phs*$cVal$mag$f" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
<Value name="相电压偏差平均值值" desc="电压偏差平均值" type="6" DO="MMXU2$MX$VolDev" DA="phs*$cVal$mag$f" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
<Value name="相电压偏差最大值值" desc="电压偏差最大值" type="6" DO="MMXU3$MX$VolDev" DA="phs*$cVal$mag$f" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
<Value name="相电压偏差最小值值" desc="电压偏差最小值" type="6" DO="MMXU4$MX$VolDev" DA="phs*$cVal$mag$f" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
<Value name="相电压总有效值95值值" desc="电压有效值95值" type="6" DO="MMXU5$MX$PhV" DA="phs*$cVal$mag$f" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="相电压总有效值平均值值" desc="电压有效值平均值" type="6" DO="MMXU2$MX$PhV" DA="phs*$cVal$mag$f" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="相电压总有效值最大值值" desc="电压有效值最大值" type="6" DO="MMXU3$MX$PhV" DA="phs*$cVal$mag$f" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="相电压总有效值最小值值" desc="电压有效值最小值" type="6" DO="MMXU4$MX$PhV" DA="phs*$cVal$mag$f" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="相电压谐波总畸变率平均值值" desc="电压总谐波畸变率平均值" type="6" DO="MHAI2$MX$ThdPhV" DA="phs*$cVal$mag$f" />
|
||||
<Value name="相电压谐波总畸变率最大值值" desc="电压总谐波畸变率最大值" type="6" DO="MHAI3$MX$ThdPhV" DA="phs*$cVal$mag$f" />
|
||||
<Value name="相电压谐波总畸变率最小值值" desc="电压总谐波畸变率最小值" type="6" DO="MHAI4$MX$ThdPhV" DA="phs*$cVal$mag$f" />
|
||||
<Value name="MIN_VFUND_ANGLE" desc="基波电压相角最小值" type="6" DO="MHAI4$MX$FundPhVAng" DA="phs*$cVal$ang$f" />
|
||||
<Value name="相电压基波有效值平均值值" desc="基波电压有效值平均值" type="6" DO="MHAI2$MX$FundPhV" DA="phs*$cVal$mag$f" />
|
||||
<Value name="相电压基波有效值最大值值" desc="基波电压有效值最大值" type="6" DO="MHAI3$MX$FundPhV" DA="phs*$cVal$mag$f" />
|
||||
<Value name="相电压基波有效值最小值值" desc="基波电压有效值最小值" type="6" DO="MHAI4$MX$FundPhV" DA="phs*$cVal$mag$f" />
|
||||
<Value name="相电压间谐波含有率序列间谐波平均值值" desc="间谐波电压含有率(f25-2475)平均值" type="6" DO="MHAI8$MX$HPhV" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="相电压间谐波含有率序列间谐波最大值值" desc="间谐波电压含有率(f25-2475)最大值" type="6" DO="MHAI9$MX$HPhV" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="相电压间谐波含有率序列间谐波最小值值" desc="间谐波电压含有率(f25-2475)最小值" type="6" DO="MHAI10$MX$HPhV" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="相电压短时闪变值波动闪变值值" desc="短时闪变" type="6" DO="MFLK1$MX$PhPst" DA="phs*$cVal$mag$f" />
|
||||
<Value name="相电压电压变动幅值波动闪变值值" desc="电压波动幅度" type="6" DO="MMXU2$MX$FLUC" DA="phs*$cVal$mag$f" />
|
||||
<Value name="相电压电压变动频度波动闪变值值" desc="电压波动频度" type="6" DO="MMXU2$MX$FLUCCF" DA="phs*$cVal$mag$f" />
|
||||
<Value name="线电压短时闪变值波动闪变值值" desc="线电压短时闪变" type="6" DO="" DA="" />
|
||||
<Value name="线电压电压变动幅值波动闪变值值" desc="线电压波动幅度" type="6" DO="" DA="" />
|
||||
<Value name="线电压电压变动频度波动闪变值值" desc="线电压波动频度" type="6" DO="" DA="" />
|
||||
<Value name="相电压长时闪变值波动闪变值值" desc="长时闪变" type="6" DO="MFLK2$MX$PhPlt" DA="phs*$cVal$mag$f" />
|
||||
<Value name="线电压长时闪变值波动闪变值值" desc="线电压长时闪变" type="6" DO="" DA="" />
|
||||
<Value name="电压扰动事件特征幅值电压变动a值" desc="残余电压" type="6" DO="QVVR0$MX$VVa" DA="mag$f" />
|
||||
<Value name="电压扰动事件持续时间电压变动a值" desc="持续时间" type="6" DO="QVVR0$MX$VVaTm" DA="mag$f" />
|
||||
|
||||
<Value name="间谐波电压有效值序列间谐波平均值值" desc="间谐波电压含有率(f25-2475)平均值" type="6" DO="MHAI8$MX$HPhV" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="间谐波电压有效值序列间谐波最大值值" desc="间谐波电压含有率(f25-2475)最大值" type="6" DO="MHAI9$MX$HPhV" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="间谐波电压有效值序列间谐波最小值值" desc="间谐波电压含有率(f25-2475)最小值" type="6" DO="MHAI10$MX$HPhV" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="相电压偏差95值值" desc="电压偏差95值" type="6" DO="MMXU5$MX$VolDev" DA="phs*$cVal$mag$f" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
<Value name="相电压偏差平均值值" desc="电压偏差平均值" type="6" DO="MMXU2$MX$VolDev" DA="phs*$cVal$mag$f" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
<Value name="相电压偏差最大值值" desc="电压偏差最大值" type="6" DO="MMXU3$MX$VolDev" DA="phs*$cVal$mag$f" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
<Value name="相电压偏差最小值值" desc="电压偏差最小值" type="6" DO="MMXU4$MX$VolDev" DA="phs*$cVal$mag$f" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
|
||||
<Value name="相电压谐波含有率序列平均值值" desc="谐波电压含有率(2-50)平均值" type="6" DO="MHAI2$MX$HPhV" DA="phs*Har[%-2]$mag$f" />
|
||||
<Value name="相电压谐波含有率序列最大值值" desc="谐波电压含有率(2-50)最大值" type="6" DO="MHAI3$MX$HPhV" DA="phs*Har[%-2]$mag$f" />
|
||||
<Value name="相电压谐波含有率序列最小值值" desc="谐波电压含有率(2-50)最小值" type="6" DO="MHAI4$MX$HPhV" DA="phs*Har[%-2]$mag$f" />
|
||||
<Value name="谐波电压有效值序列平均值角度" desc="谐波电压相角(2-50)平均值" type="6" DO="MHAI2$MX$HPhV" DA="phs*Har[%-2]$ang$f" />
|
||||
<Value name="正序负序和零序电压平均值负序值" desc="负序电压平均值" type="6" DO="MSQI2$MX$SeqV" DA="c2$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电压最大值负序值" desc="负序电压最大值" type="6" DO="MSQI3$MX$SeqV" DA="c2$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电压最小值负序值" desc="负序电压最小值" type="6" DO="MSQI4$MX$SeqV" DA="c2$cVal$mag$f" />
|
||||
<Value name="电压负序不平衡度平均值值" desc="负序电压不平衡平均值" type="6" DO="MSQI2$MX$ImbNgV" DA="mag$f" />
|
||||
<Value name="电压负序不平衡度最大值值" desc="负序电压不平衡最大值" type="6" DO="MSQI3$MX$ImbNgV" DA="mag$f" />
|
||||
<Value name="电压负序不平衡度最小值值" desc="负序电压不平衡最小值" type="6" DO="MSQI4$MX$ImbNgV" DA="mag$f" />
|
||||
<Value name="正序负序和零序电压平均值零序值" desc="零序电压平均值" type="6" DO="MSQI2$MX$SeqV" DA="c3$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电压最大值零序值" desc="零序电压最大值" type="6" DO="MSQI3$MX$SeqV" DA="c3$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电压最小值零序值" desc="零序电压最小值" type="6" DO="MSQI4$MX$SeqV" DA="c3$cVal$mag$f" />
|
||||
<Value name="电压零序不平衡度平均值值" desc="零序电压不平衡平均值" type="6" DO="MSQI2$MX$ImbZroV" DA="mag$f" />
|
||||
<Value name="电压零序不平衡度最大值值" desc="零序电压不平衡最大值" type="6" DO="MSQI3$MX$ImbZroV" DA="mag$f" />
|
||||
<Value name="电压零序不平衡度最小值值" desc="零序电压不平衡最小值" type="6" DO="MSQI4$MX$ImbZroV" DA="mag$f" />
|
||||
<Value name="频率95值值" desc="频率95值" type="6" DO="MMXU5$MX$Hz" DA="mag$f" />
|
||||
<Value name="频率平均值值" desc="频率平均值" type="6" DO="MMXU2$MX$Hz" DA="mag$f" />
|
||||
<Value name="频率最大值值" desc="频率最大值" type="6" DO="MMXU3$MX$Hz" DA="mag$f" />
|
||||
<Value name="频率最小值值" desc="频率最小值" type="6" DO="MMXU4$MX$Hz" DA="mag$f" />
|
||||
<Value name="频率偏差95值值" desc="频率偏差95值" type="6" DO="MMXU5$MX$FreqDev" DA="mag$f" />
|
||||
<Value name="频率偏差平均值值" desc="频率偏差平均值" type="6" DO="MMXU2$MX$FreqDev" DA="mag$f" />
|
||||
<Value name="频率偏差最大值值" desc="频率偏差最大值" type="6" DO="MMXU3$MX$FreqDev" DA="mag$f" />
|
||||
<Value name="频率偏差最小值值" desc="频率偏差最小值" type="6" DO="MMXU4$MX$FreqDev" DA="mag$f" />
|
||||
<Value name="正序负序和零序电压平均值正序值" desc="正序电压平均值" type="6" DO="MSQI2$MX$SeqV" DA="c1$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电压最大值正序值" desc="正序电压最大值" type="6" DO="MSQI3$MX$SeqV" DA="c1$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电压最小值正序值" desc="正序电压最小值" type="6" DO="MSQI4$MX$SeqV" DA="c1$cVal$mag$f" />
|
||||
<Value name="电流总有效值95值值" desc="电流有效值95值" type="6" DO="MMXU5$MX$A" DA="phs*$cVal$mag$f" />
|
||||
<Value name="电流总有效值平均值值" desc="电流有效值平均值" type="6" DO="MMXU2$MX$A" DA="phs*$cVal$mag$f" />
|
||||
<Value name="电流总有效值最大值值" desc="电流有效值最大值" type="6" DO="MMXU3$MX$A" DA="phs*$cVal$mag$f" />
|
||||
<Value name="电流总有效值最小值值" desc="电流有效值最小值" type="6" DO="MMXU4$MX$A" DA="phs*$cVal$mag$f" />
|
||||
<Value name="电流基波有效值平均值值" desc="基波电流有效值平均值" type="6" DO="MHAI2$MX$HA" DA="phs*Har[1]$mag$f" />
|
||||
<Value name="电流基波有效值最大值值" desc="基波电流有效值最大值" type="6" DO="MHAI3$MX$HA" DA="phs*Har[1]$mag$f" />
|
||||
<Value name="电流基波有效值最小值值" desc="基波电流有效值最小值" type="6" DO="MHAI4$MX$HA" DA="phs*Har[1]$mag$f" />
|
||||
<Value name="间谐波电流有效值序列间谐波平均值值" desc="间谐波电流幅值(f25-2475)平均值" type="6" DO="MHAI8$MX$HA" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="间谐波电流有效值序列间谐波最大值值" desc="间谐波电流幅值(f25-2475)最大值" type="6" DO="MHAI9$MX$HA" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="间谐波电流有效值序列间谐波最小值值" desc="间谐波电流幅值(f25-2475)最小值" type="6" DO="MHAI10$MX$HA" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="谐波电流有效值序列平均值值" desc="谐波电流幅值(2-50)平均值" type="6" DO="MHAI2$MX$HA" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="谐波电流有效值序列最大值值" desc="谐波电流幅值(2-50)最大值" type="6" DO="MHAI3$MX$HA" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="谐波电流有效值序列最小值值" desc="谐波电流幅值(2-50)最小值" type="6" DO="MHAI4$MX$HA" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="正序负序和零序电流平均值负序值" desc="负序电流平均值" type="6" DO="MSQI2$MX$SeqA" DA="c2$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电流最大值负序值" desc="负序电流最大值" type="6" DO="MSQI3$MX$SeqA" DA="c2$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电流最小值负序值" desc="负序电流最小值" type="6" DO="MSQI4$MX$SeqA" DA="c2$cVal$mag$f" />
|
||||
<Value name="电流负序不平衡度平均值值" desc="负序电流不平衡平均值" type="6" DO="MSQI2$MX$ImbNgA" DA="mag$f" />
|
||||
<Value name="电流负序不平衡度最大值值" desc="负序电流不平衡最大值" type="6" DO="MSQI3$MX$ImbNgA" DA="mag$f" />
|
||||
<Value name="电流负序不平衡度最小值值" desc="负序电流不平衡最小值" type="6" DO="MSQI4$MX$ImbNgA" DA="mag$f" />
|
||||
<Value name="正序负序和零序电流平均值零序值" desc="零序电流平均值" type="6" DO="MSQI2$MX$SeqA" DA="c3$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电流最大值零序值" desc="零序电流最大值" type="6" DO="MSQI3$MX$SeqA" DA="c3$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电流最小值零序值" desc="零序电流最小值" type="6" DO="MSQI4$MX$SeqA" DA="c3$cVal$mag$f" />
|
||||
<Value name="电流零序不平衡度平均值值" desc="零序电流不平衡平均值" type="6" DO="MSQI2$MX$ImbZroA" DA="mag$f" />
|
||||
<Value name="电流零序不平衡度最大值值" desc="零序电流不平衡最大值" type="6" DO="MSQI3$MX$ImbZroA" DA="mag$f" />
|
||||
<Value name="电流零序不平衡度最小值值" desc="零序电流不平衡最小值" type="6" DO="MSQI4$MX$ImbZroA" DA="mag$f" />
|
||||
<Value name="正序负序和零序电流平均值正序值" desc="正序电流平均值" type="6" DO="MSQI2$MX$SeqA" DA="c1$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电流最大值正序值" desc="正序电流最大值" type="6" DO="MSQI3$MX$SeqA" DA="c1$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电流最小值正序值" desc="正序电流最小值" type="6" DO="MSQI4$MX$SeqA" DA="c1$cVal$mag$f" />
|
||||
<Value name="功率因数平均值值" desc="功率因数平均值" type="6" DO="MMXU2$MX$PF" DA="phs*$cVal$mag$f" />
|
||||
<Value name="功率因数最大值值" desc="功率因数最大值" type="6" DO="MMXU3$MX$PF" DA="phs*$cVal$mag$f" />
|
||||
<Value name="功率因数最小值值" desc="功率因数最小值" type="6" DO="MMXU4$MX$PF" DA="phs*$cVal$mag$f" />
|
||||
<Value name="位移功率因数平均值值" desc="基波功率因数平均值" type="6" DO="MHAI2$MX$HSigPF" DA="phs*Har[1]$mag$f" />
|
||||
<Value name="位移功率因数最大值值" desc="基波功率因数最大值" type="6" DO="MHAI3$MX$HSigPF" DA="phs*Har[1]$mag$f" />
|
||||
<Value name="基波有功功率平均值值" desc="基波有功功率平均值" type="6" DO="MHAI2$MX$HW" DA="phs*Har[1]$mag$f" />
|
||||
<Value name="基波有功功率最大值值" desc="基波有功功率最大值" type="6" DO="MHAI3$MX$HW" DA="phs*Har[1]$mag$f" />
|
||||
<Value name="基波有功功率最小值值" desc="基波有功功率最小值" type="6" DO="MHAI4$MX$HW" DA="phs*Har[1]$mag$f" />
|
||||
<Value name="视在功率95值值" desc="视在功率95值" type="6" DO="MMXU5$MX$VA" DA="phs*$cVal$mag$f" />
|
||||
<Value name="视在功率平均值值" desc="视在功率平均值" type="6" DO="MMXU2$MX$VA" DA="phs*$cVal$mag$f" />
|
||||
<Value name="视在功率最大值值" desc="视在功率最大值" type="6" DO="MMXU3$MX$VA" DA="phs*$cVal$mag$f" />
|
||||
<Value name="视在功率最小值值" desc="视在功率最小值" type="6" DO="MMXU4$MX$VA" DA="phs*$cVal$mag$f" />
|
||||
<Value name="无功功率95值值" desc="无功功率95值" type="6" DO="MMXU5$MX$VAr" DA="phs*$cVal$mag$f" />
|
||||
<Value name="无功功率平均值值" desc="无功功率平均值" type="6" DO="MMXU2$MX$VAr" DA="phs*$cVal$mag$f" />
|
||||
<Value name="无功功率最大值值" desc="无功功率最大值" type="6" DO="MMXU3$MX$VAr" DA="phs*$cVal$mag$f" />
|
||||
<Value name="无功功率最小值值" desc="无功功率最小值" type="6" DO="MMXU4$MX$VAr" DA="phs*$cVal$mag$f" />
|
||||
<Value name="2~50次谐波有功功率序列平均值值" desc="谐波有功功率(2-50)平均值" type="6" DO="MHAI2$MX$HW" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="2~50次谐波有功功率序列最大值值" desc="谐波有功功率(2-50)最大值" type="6" DO="MHAI3$MX$HW" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="2~50次谐波有功功率序列最小值值" desc="谐波有功功率(2-50)最小值" type="6" DO="MHAI4$MX$HW" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="有功功率95值值" desc="有功功率95值" type="6" DO="MMXU5$MX$W" DA="phs*$cVal$mag$f" />
|
||||
<Value name="有功功率平均值值" desc="有功功率平均值" type="6" DO="MMXU2$MX$W" DA="phs*$cVal$mag$f" />
|
||||
<Value name="有功功率最大值值" desc="有功功率最大值" type="6" DO="MMXU3$MX$W" DA="phs*$cVal$mag$f" />
|
||||
<Value name="有功功率最小值值" desc="有功功率最小值" type="6" DO="MMXU4$MX$W" DA="phs*$cVal$mag$f" />
|
||||
<Value name="三相功率因数平均值值" desc="总功率因数平均值" type="6" DO="MMXU2$MX$TotPF" DA="mag$f" />
|
||||
<Value name="三相功率因数最大值值" desc="总功率因数最大值" type="6" DO="MMXU3$MX$TotPF" DA="mag$f" />
|
||||
<Value name="三相功率因数最小值值" desc="总功率因数最小值" type="6" DO="MMXU4$MX$TotPF" DA="mag$f" />
|
||||
<Value name="三相位移功率因数平均值值" desc="总基波功率因数平均值" type="6" DO="MHAI2$MX$HTotPF" DA="har[1]$mag$f" />
|
||||
<Value name="三相位移功率因数最大值值" desc="总基波功率因数最大值" type="6" DO="MHAI3$MX$HTotPF" DA="har[1]$mag$f" />
|
||||
<Value name="三相位移功率因数最小值值" desc="总基波功率因数最小值" type="6" DO="MHAI4$MX$HTotPF" DA="har[1]$mag$f" />
|
||||
<Value name="三相总视在功率平均值值" desc="总视在功率平均值" type="6" DO="MMXU2$MX$TotVA" DA="mag$f" />
|
||||
<Value name="三相总视在功率最大值值" desc="总视在功率最大值" type="6" DO="MMXU3$MX$TotVA" DA="mag$f" />
|
||||
<Value name="三相总视在功率最小值值" desc="总视在功率最小值" type="6" DO="MMXU4$MX$TotVA" DA="mag$f" />
|
||||
<Value name="三相总无功功率平均值值" desc="总无功功率平均值" type="6" DO="MMXU2$MX$TotVAr" DA="mag$f" />
|
||||
<Value name="三相总无功功率最大值值" desc="总无功功率最大值" type="6" DO="MMXU3$MX$TotVAr" DA="mag$f" />
|
||||
<Value name="三相总无功功率最小值值" desc="总无功功率最小值" type="6" DO="MMXU4$MX$TotVAr" DA="mag$f" />
|
||||
<Value name="三相总有功功率平均值值" desc="总有功功率平均值" type="6" DO="MMXU2$MX$TotW" DA="mag$f" />
|
||||
<Value name="三相总有功功率最大值值" desc="总有功功率最大值" type="6" DO="MMXU3$MX$TotW" DA="mag$f" />
|
||||
<Value name="三相总有功功率最小值值" desc="总有功功率最小值" type="6" DO="MMXU4$MX$TotW" DA="mag$f" />
|
||||
<Value name="相电压总有效值95值值" desc="电压有效值95值" type="6" DO="MMXU5$MX$PhV" DA="phs*$cVal$mag$f" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="相电压总有效值平均值值" desc="电压有效值平均值" type="6" DO="MMXU2$MX$PhV" DA="phs*$cVal$mag$f" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="相电压总有效值最大值值" desc="电压有效值最大值" type="6" DO="MMXU3$MX$PhV" DA="phs*$cVal$mag$f" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="相电压总有效值最小值值" desc="电压有效值最小值" type="6" DO="MMXU4$MX$PhV" DA="phs*$cVal$mag$f" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
|
||||
<Value name="线电压总有效值95值值" desc="线电压有效值95值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="线电压总有效值平均值值" desc="线电压有效值平均值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="线电压总有效值最大值值" desc="线电压有效值最大值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="线电压总有效值最小值值" desc="线电压有效值最小值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
|
||||
<Value name="相电压谐波总畸变率95值值" desc="电压总谐波畸变率95值" type="6" DO="" DA="" />
|
||||
<Value name="相电压谐波总畸变率平均值值" desc="电压总谐波畸变率平均值" type="6" DO="MHAI2$MX$ThdPhV" DA="phs*$cVal$mag$f" />
|
||||
<Value name="相电压谐波总畸变率最大值值" desc="电压总谐波畸变率最大值" type="6" DO="MHAI3$MX$ThdPhV" DA="phs*$cVal$mag$f" />
|
||||
<Value name="相电压谐波总畸变率最小值值" desc="电压总谐波畸变率最小值" type="6" DO="MHAI4$MX$ThdPhV" DA="phs*$cVal$mag$f" />
|
||||
|
||||
<Value name="相电压基波有效值95值角度" desc="基波电压相角95值" type="6" DO="" DA="" />
|
||||
<Value name="相电压基波有效值平均值角度" desc="基波电压相角平均值" type="6" DO="MHAI2$MX$FundPhVAng" DA="phs*$cVal$ang$f" />
|
||||
<Value name="相电压基波有效值最大值角度" desc="基波电压相角最大值" type="6" DO="" DA="" />
|
||||
<Value name="相电压基波有效值最小值角度" desc="基波电压相角最小值" type="6" DO="MHAI4$MX$FundPhVAng" DA="phs*$cVal$ang$f" />
|
||||
|
||||
<Value name="相电压基波有效值95值值" desc="基波电压有效值95值" type="6" DO="" DA="" />
|
||||
<Value name="相电压基波有效值平均值值" desc="基波电压有效值平均值" type="6" DO="MHAI2$MX$FundPhV" DA="phs*$cVal$mag$f" />
|
||||
<Value name="相电压基波有效值最大值值" desc="基波电压有效值最大值" type="6" DO="MHAI3$MX$FundPhV" DA="phs*$cVal$mag$f" />
|
||||
<Value name="相电压基波有效值最小值值" desc="基波电压有效值最小值" type="6" DO="MHAI4$MX$FundPhV" DA="phs*$cVal$mag$f" />
|
||||
|
||||
<Value name="间谐波电压有效值序列间谐波95值值" desc="间谐波电压含有率(f25-2475)95值" type="6" DO="MHAI8$MX$HPhV" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="间谐波电压有效值序列间谐波平均值值" desc="间谐波电压含有率(f25-2475)平均值" type="6" DO="MHAI8$MX$HPhV" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="间谐波电压有效值序列间谐波最大值值" desc="间谐波电压含有率(f25-2475)最大值" type="6" DO="MHAI9$MX$HPhV" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="间谐波电压有效值序列间谐波最小值值" desc="间谐波电压含有率(f25-2475)最小值" type="6" DO="MHAI10$MX$HPhV" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="间谐波电压有效值序列间谐波95值值" desc="间谐波电压含有率(f25-2475)95值" type="6" DO="MHAI8$MX$HPhV" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="间谐波电压有效值序列间谐波平均值值" desc="间谐波电压含有率(f25-2475)平均值" type="6" DO="MHAI8$MX$HPhV" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="间谐波电压有效值序列间谐波最大值值" desc="间谐波电压含有率(f25-2475)最大值" type="6" DO="MHAI9$MX$HPhV" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="间谐波电压有效值序列间谐波最小值值" desc="间谐波电压含有率(f25-2475)最小值" type="6" DO="MHAI10$MX$HPhV" DA="phs*Har[%-0]$mag$f" />
|
||||
|
||||
<Value name="相电压谐波含有率序列95值值" desc="谐波电压含有率(2-50)95值" type="6" DO="MHAI2$MX$HPhV" DA="phs*Har[%-2]$mag$f" />
|
||||
<Value name="相电压谐波含有率序列平均值值" desc="谐波电压含有率(2-50)平均值" type="6" DO="MHAI2$MX$HPhV" DA="phs*Har[%-2]$mag$f" />
|
||||
<Value name="相电压谐波含有率序列最大值值" desc="谐波电压含有率(2-50)最大值" type="6" DO="MHAI3$MX$HPhV" DA="phs*Har[%-2]$mag$f" />
|
||||
<Value name="相电压谐波含有率序列最小值值" desc="谐波电压含有率(2-50)最小值" type="6" DO="MHAI4$MX$HPhV" DA="phs*Har[%-2]$mag$f" />
|
||||
|
||||
<Value name="谐波电压有效值序列95值角度" desc="谐波电压相角(2-50)95值" type="6" DO="MHAI2$MX$HPhV" DA="phs*Har[%-2]$ang$f" />
|
||||
<Value name="谐波电压有效值序列平均值角度" desc="谐波电压相角(2-50)平均值" type="6" DO="MHAI2$MX$HPhV" DA="phs*Har[%-2]$ang$f" />
|
||||
<Value name="谐波电压有效值序列最大值角度" desc="谐波电压相角(2-50)最大值" type="6" DO="MHAI3$MX$HPhV" DA="phs*Har[%-2]$ang$f" />
|
||||
<Value name="谐波电压有效值序列最小值角度" desc="谐波电压相角(2-50)最小值" type="6" DO="MHAI4$MX$HPhV" DA="phs*Har[%-2]$ang$f" />
|
||||
<Value name="相电压谐波含有率序列95值角度" desc="谐波电压相角(2-50)95值" type="6" DO="MHAI2$MX$HPhV" DA="phs*Har[%-2]$ang$f" />
|
||||
<Value name="相电压谐波含有率序列平均值角度" desc="谐波电压相角(2-50)平均值" type="6" DO="MHAI2$MX$HPhV" DA="phs*Har[%-2]$ang$f" />
|
||||
<Value name="相电压谐波含有率序列最大值角度" desc="谐波电压相角(2-50)最大值" type="6" DO="MHAI3$MX$HPhV" DA="phs*Har[%-2]$ang$f" />
|
||||
<Value name="相电压谐波含有率序列最小值角度" desc="谐波电压相角(2-50)最小值" type="6" DO="MHAI4$MX$HPhV" DA="phs*Har[%-2]$ang$f" />
|
||||
|
||||
<Value name="线电压偏差95值值" desc="线电压偏差95值" type="6" DO="" DA="" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
<Value name="线电压偏差平均值值" desc="线电压偏差平均值" type="6" DO="" DA="" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
<Value name="线电压偏差最大值值" desc="线电压偏差最大值" type="6" DO="" DA="" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
<Value name="线电压偏差最小值值" desc="线电压偏差最小值" type="6" DO="" DA="" BaseFlag="1" LimitUp="20" LimitDown="-20" Coefficient="1"/>
|
||||
|
||||
<Value name="线电压总有效值95值值" desc="线电压有效值95值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="线电压总有效值平均值值" desc="线电压有效值平均值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="线电压总有效值最大值值" desc="线电压有效值最大值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="线电压总有效值最小值值" desc="线电压有效值最小值" type="6" DO="" DA="" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
|
||||
<Value name="线电压谐波总畸变率95值值" desc="线电压总谐波畸变率95值" type="6" DO="" DA="" />
|
||||
<Value name="线电压谐波总畸变率平均值值" desc="线电压总谐波畸变率平均值" type="6" DO="" DA="" />
|
||||
<Value name="线电压谐波总畸变率最大值值" desc="线电压总谐波畸变率最大值" type="6" DO="" DA="" />
|
||||
<Value name="线电压谐波总畸变率最小值值" desc="线电压总谐波畸变率最小值" type="6" DO="" DA="" />
|
||||
|
||||
<Value name="线电压基波有效值95值角度" desc="基波线电压相角95值" type="6" DO="" DA="" />
|
||||
<Value name="线电压基波有效值平均值角度" desc="基波线电压相角平均值" type="6" DO="" DA="" />
|
||||
<Value name="线电压基波有效值最大值角度" desc="基波线电压相角最大值" type="6" DO="" DA="" />
|
||||
<Value name="线电压基波有效值最小值角度" desc="基波线电压相角最小值" type="6" DO="" DA="" />
|
||||
|
||||
<Value name="线电压基波有效值95值值" desc="基波线电压有效值95值" type="6" DO="" DA="" />
|
||||
<Value name="线电压基波有效值平均值值" desc="基波线电压有效值平均值" type="6" DO="" DA="" />
|
||||
<Value name="线电压基波有效值最大值值" desc="基波线电压有效值最大值" type="6" DO="" DA="" />
|
||||
<Value name="线电压基波有效值最小值值" desc="基波线电压有效值最小值" type="6" DO="" DA="" />
|
||||
|
||||
<Value name="线电压间谐波含有率序列间谐波95值值" desc="间谐波线电压含有率(f25-2475)95值" type="6" DO="" DA="" />
|
||||
<Value name="线电压间谐波含有率序列间谐波平均值值" desc="间谐波线电压含有率(f25-2475)平均值" type="6" DO="" DA="" />
|
||||
<Value name="线电压间谐波含有率序列间谐波最大值值" desc="间谐波线电压含有率(f25-2475)最大值" type="6" DO="" DA="" />
|
||||
<Value name="线电压间谐波含有率序列间谐波最小值值" desc="间谐波线电压含有率(f25-2475)最小值" type="6" DO="" DA="" />
|
||||
|
||||
<Value name="线电压谐波含有率序列95值值" desc="谐波线电压含有率(2-50)95值" type="6" DO="" DA="" />
|
||||
<Value name="线电压谐波含有率序列平均值值" desc="谐波线电压含有率(2-50)平均值" type="6" DO="" DA="" />
|
||||
<Value name="线电压谐波含有率序列最大值值" desc="谐波线电压含有率(2-50)最大值" type="6" DO="" DA="" />
|
||||
<Value name="线电压谐波含有率序列最小值值" desc="谐波线电压含有率(2-50)最小值" type="6" DO="" DA="" />
|
||||
|
||||
<Value name="线电压谐波含有率序列95值角度" desc="谐波线电压相角(2-50)95值" type="6" DO="" DA="" />
|
||||
<Value name="线电压谐波含有率序列平均值角度" desc="谐波线电压相角(2-50)平均值" type="6" DO="" DA="" />
|
||||
<Value name="线电压谐波含有率序列最大值角度" desc="谐波线电压相角(2-50)最大值" type="6" DO="" DA="" />
|
||||
<Value name="线电压谐波含有率序列最小值角度" desc="谐波线电压相角(2-50)最小值" type="6" DO="" DA="" />
|
||||
|
||||
<Value name="正序负序和零序电压95值负序值" desc="负序电压95值" type="6" DO="MSQI2$MX$SeqV" DA="c2$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电压平均值负序值" desc="负序电压平均值" type="6" DO="MSQI2$MX$SeqV" DA="c2$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电压最大值负序值" desc="负序电压最大值" type="6" DO="MSQI3$MX$SeqV" DA="c2$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电压最小值负序值" desc="负序电压最小值" type="6" DO="MSQI4$MX$SeqV" DA="c2$cVal$mag$f" />
|
||||
|
||||
<Value name="电压负序不平衡度95值值" desc="负序电压不平衡95值" type="6" DO="MSQI2$MX$ImbNgV" DA="mag$f" />
|
||||
<Value name="电压负序不平衡度平均值值" desc="负序电压不平衡平均值" type="6" DO="MSQI2$MX$ImbNgV" DA="mag$f" />
|
||||
<Value name="电压负序不平衡度最大值值" desc="负序电压不平衡最大值" type="6" DO="MSQI3$MX$ImbNgV" DA="mag$f" />
|
||||
<Value name="电压负序不平衡度最小值值" desc="负序电压不平衡最小值" type="6" DO="MSQI4$MX$ImbNgV" DA="mag$f" />
|
||||
|
||||
<Value name="正序负序和零序电压95值零序值" desc="零序电压95值" type="6" DO="MSQI2$MX$SeqV" DA="c3$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电压平均值零序值" desc="零序电压平均值" type="6" DO="MSQI2$MX$SeqV" DA="c3$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电压最大值零序值" desc="零序电压最大值" type="6" DO="MSQI3$MX$SeqV" DA="c3$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电压最小值零序值" desc="零序电压最小值" type="6" DO="MSQI4$MX$SeqV" DA="c3$cVal$mag$f" />
|
||||
|
||||
<Value name="电压零序不平衡度95值值" desc="零序电压不平衡95值" type="6" DO="MSQI2$MX$ImbZroV" DA="mag$f" />
|
||||
<Value name="电压零序不平衡度平均值值" desc="零序电压不平衡平均值" type="6" DO="MSQI2$MX$ImbZroV" DA="mag$f" />
|
||||
<Value name="电压零序不平衡度最大值值" desc="零序电压不平衡最大值" type="6" DO="MSQI3$MX$ImbZroV" DA="mag$f" />
|
||||
<Value name="电压零序不平衡度最小值值" desc="零序电压不平衡最小值" type="6" DO="MSQI4$MX$ImbZroV" DA="mag$f" />
|
||||
|
||||
<Value name="频率95值值" desc="频率95值" type="6" DO="MMXU5$MX$Hz" DA="mag$f" />
|
||||
<Value name="频率平均值值" desc="频率平均值" type="6" DO="MMXU2$MX$Hz" DA="mag$f" />
|
||||
<Value name="频率最大值值" desc="频率最大值" type="6" DO="MMXU3$MX$Hz" DA="mag$f" />
|
||||
<Value name="频率最小值值" desc="频率最小值" type="6" DO="MMXU4$MX$Hz" DA="mag$f" />
|
||||
|
||||
<Value name="频率偏差95值值" desc="频率偏差95值" type="6" DO="MMXU5$MX$FreqDev" DA="mag$f" />
|
||||
<Value name="频率偏差平均值值" desc="频率偏差平均值" type="6" DO="MMXU2$MX$FreqDev" DA="mag$f" />
|
||||
<Value name="频率偏差最大值值" desc="频率偏差最大值" type="6" DO="MMXU3$MX$FreqDev" DA="mag$f" />
|
||||
<Value name="频率偏差最小值值" desc="频率偏差最小值" type="6" DO="MMXU4$MX$FreqDev" DA="mag$f" />
|
||||
|
||||
<Value name="正序负序和零序电压95值正序值" desc="正序电压95值" type="6" DO="MSQI2$MX$SeqV" DA="c1$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电压平均值正序值" desc="正序电压平均值" type="6" DO="MSQI2$MX$SeqV" DA="c1$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电压最大值正序值" desc="正序电压最大值" type="6" DO="MSQI3$MX$SeqV" DA="c1$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电压最小值正序值" desc="正序电压最小值" type="6" DO="MSQI4$MX$SeqV" DA="c1$cVal$mag$f" />
|
||||
|
||||
<Value name="电流总有效值95值值" desc="电流有效值95值" type="6" DO="MMXU5$MX$A" DA="phs*$cVal$mag$f" />
|
||||
<Value name="电流总有效值平均值值" desc="电流有效值平均值" type="6" DO="MMXU2$MX$A" DA="phs*$cVal$mag$f" />
|
||||
<Value name="电流总有效值最大值值" desc="电流有效值最大值" type="6" DO="MMXU3$MX$A" DA="phs*$cVal$mag$f" />
|
||||
<Value name="电流总有效值最小值值" desc="电流有效值最小值" type="6" DO="MMXU4$MX$A" DA="phs*$cVal$mag$f" />
|
||||
|
||||
<Value name="电流基波有效值95值值" desc="基波电流有效值95值" type="6" DO="MHAI2$MX$HA" DA="phs*Har[1]$mag$f" />
|
||||
<Value name="电流基波有效值平均值值" desc="基波电流有效值平均值" type="6" DO="MHAI2$MX$HA" DA="phs*Har[1]$mag$f" />
|
||||
<Value name="电流基波有效值最大值值" desc="基波电流有效值最大值" type="6" DO="MHAI3$MX$HA" DA="phs*Har[1]$mag$f" />
|
||||
<Value name="电流基波有效值最小值值" desc="基波电流有效值最小值" type="6" DO="MHAI4$MX$HA" DA="phs*Har[1]$mag$f" />
|
||||
|
||||
<Value name="电流基波有效值95值角度" desc="基波电流相角95值" type="6" DO="" DA="" />
|
||||
<Value name="电流基波有效值平均值角度" desc="基波电流相角平均值" type="6" DO="" DA="" />
|
||||
<Value name="电流基波有效值最大值角度" desc="基波电流相角最大值" type="6" DO="" DA="" />
|
||||
<Value name="电流基波有效值最小值角度" desc="基波电流相角最小值" type="6" DO="" DA="" />
|
||||
|
||||
<Value name="间谐波电流有效值序列间谐波95值值" desc="间谐波电流幅值(f25-2475)95值" type="6" DO="MHAI8$MX$HA" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="间谐波电流有效值序列间谐波平均值值" desc="间谐波电流幅值(f25-2475)平均值" type="6" DO="MHAI8$MX$HA" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="间谐波电流有效值序列间谐波最大值值" desc="间谐波电流幅值(f25-2475)最大值" type="6" DO="MHAI9$MX$HA" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="间谐波电流有效值序列间谐波最小值值" desc="间谐波电流幅值(f25-2475)最小值" type="6" DO="MHAI10$MX$HA" DA="phs*Har[%-0]$mag$f" />
|
||||
|
||||
<Value name="谐波电流有效值序列95值值" desc="谐波电流幅值(2-50)95值" type="6" DO="MHAI2$MX$HA" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="谐波电流有效值序列平均值值" desc="谐波电流幅值(2-50)平均值" type="6" DO="MHAI2$MX$HA" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="谐波电流有效值序列最大值值" desc="谐波电流幅值(2-50)最大值" type="6" DO="MHAI3$MX$HA" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="谐波电流有效值序列最小值值" desc="谐波电流幅值(2-50)最小值" type="6" DO="MHAI4$MX$HA" DA="phs*Har[%-0]$mag$f" />
|
||||
|
||||
<Value name="谐波电流有效值序列95值角度" desc="谐波电流相角(2-50)95值" type="6" DO="" DA="" />
|
||||
<Value name="谐波电流有效值序列平均值角度" desc="谐波电流相角(2-50)平均值" type="6" DO="" DA="" />
|
||||
<Value name="谐波电流有效值序列最大值角度" desc="谐波电流相角(2-50)最大值" type="6" DO="" DA="" />
|
||||
<Value name="谐波电流有效值序列最小值角度" desc="谐波电流相角(2-50)最小值" type="6" DO="" DA="" />
|
||||
|
||||
<Value name="电流总谐波畸变率平均值值" desc="电流总谐波畸变率平均值" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="电流总谐波畸变率最大值值" desc="电流总谐波畸变率最大值" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="电流总谐波畸变率最小值值" desc="电流总谐波畸变率最小值" type="6" DO="" DA="" Coefficient="1" />
|
||||
<Value name="电流总谐波畸变率95值值" desc="电流总谐波畸变率95值" type="6" DO="" DA="" Coefficient="1" />
|
||||
|
||||
<Value name="正序负序和零序电流95值负序值" desc="负序电流95值" type="6" DO="MSQI2$MX$SeqA" DA="c2$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电流平均值负序值" desc="负序电流平均值" type="6" DO="MSQI2$MX$SeqA" DA="c2$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电流最大值负序值" desc="负序电流最大值" type="6" DO="MSQI3$MX$SeqA" DA="c2$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电流最小值负序值" desc="负序电流最小值" type="6" DO="MSQI4$MX$SeqA" DA="c2$cVal$mag$f" />
|
||||
|
||||
<Value name="电流负序不平衡度95值值" desc="负序电流不平衡95值" type="6" DO="MSQI2$MX$ImbNgA" DA="mag$f" />
|
||||
<Value name="电流负序不平衡度平均值值" desc="负序电流不平衡平均值" type="6" DO="MSQI2$MX$ImbNgA" DA="mag$f" />
|
||||
<Value name="电流负序不平衡度最大值值" desc="负序电流不平衡最大值" type="6" DO="MSQI3$MX$ImbNgA" DA="mag$f" />
|
||||
<Value name="电流负序不平衡度最小值值" desc="负序电流不平衡最小值" type="6" DO="MSQI4$MX$ImbNgA" DA="mag$f" />
|
||||
|
||||
<Value name="正序负序和零序电流95值零序值" desc="零序电流95值" type="6" DO="MSQI2$MX$SeqA" DA="c3$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电流平均值零序值" desc="零序电流平均值" type="6" DO="MSQI2$MX$SeqA" DA="c3$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电流最大值零序值" desc="零序电流最大值" type="6" DO="MSQI3$MX$SeqA" DA="c3$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电流最小值零序值" desc="零序电流最小值" type="6" DO="MSQI4$MX$SeqA" DA="c3$cVal$mag$f" />
|
||||
|
||||
<Value name="电流零序不平衡度95值值" desc="零序电流不平衡95值" type="6" DO="MSQI2$MX$ImbZroA" DA="mag$f" />
|
||||
<Value name="电流零序不平衡度平均值值" desc="零序电流不平衡平均值" type="6" DO="MSQI2$MX$ImbZroA" DA="mag$f" />
|
||||
<Value name="电流零序不平衡度最大值值" desc="零序电流不平衡最大值" type="6" DO="MSQI3$MX$ImbZroA" DA="mag$f" />
|
||||
<Value name="电流零序不平衡度最小值值" desc="零序电流不平衡最小值" type="6" DO="MSQI4$MX$ImbZroA" DA="mag$f" />
|
||||
|
||||
<Value name="正序负序和零序电流95值正序值" desc="正序电流95值" type="6" DO="MSQI2$MX$SeqA" DA="c1$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电流平均值正序值" desc="正序电流平均值" type="6" DO="MSQI2$MX$SeqA" DA="c1$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电流最大值正序值" desc="正序电流最大值" type="6" DO="MSQI3$MX$SeqA" DA="c1$cVal$mag$f" />
|
||||
<Value name="正序负序和零序电流最小值正序值" desc="正序电流最小值" type="6" DO="MSQI4$MX$SeqA" DA="c1$cVal$mag$f" />
|
||||
|
||||
<Value name="功率因数95值值" desc="功率因数95值" type="6" DO="MMXU2$MX$PF" DA="phs*$cVal$mag$f" />
|
||||
<Value name="功率因数平均值值" desc="功率因数平均值" type="6" DO="MMXU2$MX$PF" DA="phs*$cVal$mag$f" />
|
||||
<Value name="功率因数最大值值" desc="功率因数最大值" type="6" DO="MMXU3$MX$PF" DA="phs*$cVal$mag$f" />
|
||||
<Value name="功率因数最小值值" desc="功率因数最小值" type="6" DO="MMXU4$MX$PF" DA="phs*$cVal$mag$f" />
|
||||
|
||||
<Value name="位移功率因数平均值值" desc="基波功率因数平均值" type="6" DO="MHAI2$MX$HSigPF" DA="phs*Har[1]$mag$f" />
|
||||
<Value name="位移功率因数最大值值" desc="基波功率因数最大值" type="6" DO="MHAI3$MX$HSigPF" DA="phs*Har[1]$mag$f" />
|
||||
<Value name="三相位移功率因数平均值值" desc="总基波功率因数平均值" type="6" DO="MHAI2$MX$HTotPF" DA="har[1]$mag$f" />
|
||||
<Value name="三相位移功率因数最大值值" desc="总基波功率因数最大值" type="6" DO="MHAI3$MX$HTotPF" DA="har[1]$mag$f" />
|
||||
<Value name="三相位移功率因数最小值值" desc="总基波功率因数最小值" type="6" DO="MHAI4$MX$HTotPF" DA="har[1]$mag$f" />
|
||||
|
||||
<Value name="位移功率因数95值值" desc="位移功率因数95值" type="6" DO="MHAI2$MX$HSigPF" DA="phs*Har[1]$mag$f" />
|
||||
<Value name="位移功率因数平均值值" desc="位移功率因数平均值" type="6" DO="MHAI2$MX$HSigPF" DA="phs*Har[1]$mag$f" />
|
||||
<Value name="位移功率因数最大值值" desc="位移功率因数最大值" type="6" DO="MHAI3$MX$HSigPF" DA="phs*Har[1]$mag$f" />
|
||||
<Value name="位移功率因数最小值值" desc="位移功率因数最小值" type="6" DO="MHAI4$MX$HSigPF" DA="phs*Har[1]$mag$f" />
|
||||
|
||||
<Value name="基波有功功率95值值" desc="基波有功功率95值" type="6" DO="MHAI2$MX$HW" DA="phs*Har[1]$mag$f" />
|
||||
<Value name="基波有功功率平均值值" desc="基波有功功率平均值" type="6" DO="MHAI2$MX$HW" DA="phs*Har[1]$mag$f" />
|
||||
<Value name="基波有功功率最大值值" desc="基波有功功率最大值" type="6" DO="MHAI3$MX$HW" DA="phs*Har[1]$mag$f" />
|
||||
<Value name="基波有功功率最小值值" desc="基波有功功率最小值" type="6" DO="MHAI4$MX$HW" DA="phs*Har[1]$mag$f" />
|
||||
|
||||
<Value name="视在功率95值值" desc="视在功率95值" type="6" DO="MMXU5$MX$VA" DA="phs*$cVal$mag$f" />
|
||||
<Value name="视在功率平均值值" desc="视在功率平均值" type="6" DO="MMXU2$MX$VA" DA="phs*$cVal$mag$f" />
|
||||
<Value name="视在功率最大值值" desc="视在功率最大值" type="6" DO="MMXU3$MX$VA" DA="phs*$cVal$mag$f" />
|
||||
<Value name="视在功率最小值值" desc="视在功率最小值" type="6" DO="MMXU4$MX$VA" DA="phs*$cVal$mag$f" />
|
||||
|
||||
<Value name="无功功率95值值" desc="无功功率95值" type="6" DO="MMXU5$MX$VAr" DA="phs*$cVal$mag$f" />
|
||||
<Value name="无功功率平均值值" desc="无功功率平均值" type="6" DO="MMXU2$MX$VAr" DA="phs*$cVal$mag$f" />
|
||||
<Value name="无功功率最大值值" desc="无功功率最大值" type="6" DO="MMXU3$MX$VAr" DA="phs*$cVal$mag$f" />
|
||||
<Value name="无功功率最小值值" desc="无功功率最小值" type="6" DO="MMXU4$MX$VAr" DA="phs*$cVal$mag$f" />
|
||||
|
||||
<Value name="2~50次谐波有功功率序列95值值" desc="谐波有功功率(2-50)95值" type="6" DO="MHAI2$MX$HW" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="2~50次谐波有功功率序列平均值值" desc="谐波有功功率(2-50)平均值" type="6" DO="MHAI2$MX$HW" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="2~50次谐波有功功率序列最大值值" desc="谐波有功功率(2-50)最大值" type="6" DO="MHAI3$MX$HW" DA="phs*Har[%-0]$mag$f" />
|
||||
<Value name="2~50次谐波有功功率序列最小值值" desc="谐波有功功率(2-50)最小值" type="6" DO="MHAI4$MX$HW" DA="phs*Har[%-0]$mag$f" />
|
||||
|
||||
<Value name="2~50次谐波无功功率序列95值值" desc="谐波无功功率(2-50)95值" type="6" DO="" DA="" />
|
||||
<Value name="2~50次谐波无功功率序列平均值值" desc="谐波无功功率(2-50)平均值" type="6" DO="" DA="" />
|
||||
<Value name="2~50次谐波无功功率序列最大值值" desc="谐波无功功率(2-50)最大值" type="6" DO="" DA="" />
|
||||
<Value name="2~50次谐波无功功率序列最小值值" desc="谐波无功功率(2-50)最小值" type="6" DO="" DA="" />
|
||||
|
||||
<Value name="2~50次谐波视在功率序列95值值" desc="谐波视在功率(2-50)95值" type="6" DO="" DA="" />
|
||||
<Value name="2~50次谐波视在功率序列平均值值" desc="谐波视在功率(2-50)平均值" type="6" DO="" DA="" />
|
||||
<Value name="2~50次谐波视在功率序列最大值值" desc="谐波视在功率(2-50)最大值" type="6" DO="" DA="" />
|
||||
<Value name="2~50次谐波视在功率序列最小值值" desc="谐波视在功率(2-50)最小值" type="6" DO="" DA="" />
|
||||
|
||||
<Value name="有功功率95值值" desc="有功功率95值" type="6" DO="MMXU5$MX$W" DA="phs*$cVal$mag$f" />
|
||||
<Value name="有功功率平均值值" desc="有功功率平均值" type="6" DO="MMXU2$MX$W" DA="phs*$cVal$mag$f" />
|
||||
<Value name="有功功率最大值值" desc="有功功率最大值" type="6" DO="MMXU3$MX$W" DA="phs*$cVal$mag$f" />
|
||||
<Value name="有功功率最小值值" desc="有功功率最小值" type="6" DO="MMXU4$MX$W" DA="phs*$cVal$mag$f" />
|
||||
|
||||
<Value name="三相功率因数95值值" desc="总功率因数95值" type="6" DO="MMXU2$MX$TotPF" DA="mag$f" />
|
||||
<Value name="三相功率因数平均值值" desc="总功率因数平均值" type="6" DO="MMXU2$MX$TotPF" DA="mag$f" />
|
||||
<Value name="三相功率因数最大值值" desc="总功率因数最大值" type="6" DO="MMXU3$MX$TotPF" DA="mag$f" />
|
||||
<Value name="三相功率因数最小值值" desc="总功率因数最小值" type="6" DO="MMXU4$MX$TotPF" DA="mag$f" />
|
||||
|
||||
<Value name="三相位移功率因数95值值" desc="总位移功率因数95值" type="6" DO="MHAI2$MX$HTotPF" DA="har[1]$mag$f" />
|
||||
<Value name="三相位移功率因数平均值值" desc="总位移功率因数平均值" type="6" DO="MHAI2$MX$HTotPF" DA="har[1]$mag$f" />
|
||||
<Value name="三相位移功率因数最大值值" desc="总位移功率因数最大值" type="6" DO="MHAI3$MX$HTotPF" DA="har[1]$mag$f" />
|
||||
<Value name="三相位移功率因数最小值值" desc="总位移功率因数最小值" type="6" DO="MHAI4$MX$HTotPF" DA="har[1]$mag$f" />
|
||||
|
||||
<Value name="三相总视在功率95值值" desc="总视在功率95值" type="6" DO="MMXU2$MX$TotVA" DA="mag$f" />
|
||||
<Value name="三相总视在功率平均值值" desc="总视在功率平均值" type="6" DO="MMXU2$MX$TotVA" DA="mag$f" />
|
||||
<Value name="三相总视在功率最大值值" desc="总视在功率最大值" type="6" DO="MMXU3$MX$TotVA" DA="mag$f" />
|
||||
<Value name="三相总视在功率最小值值" desc="总视在功率最小值" type="6" DO="MMXU4$MX$TotVA" DA="mag$f" />
|
||||
|
||||
<Value name="三相总无功功率95值值" desc="总无功功率95值" type="6" DO="MMXU2$MX$TotVAr" DA="mag$f" />
|
||||
<Value name="三相总无功功率平均值值" desc="总无功功率平均值" type="6" DO="MMXU2$MX$TotVAr" DA="mag$f" />
|
||||
<Value name="三相总无功功率最大值值" desc="总无功功率最大值" type="6" DO="MMXU3$MX$TotVAr" DA="mag$f" />
|
||||
<Value name="三相总无功功率最小值值" desc="总无功功率最小值" type="6" DO="MMXU4$MX$TotVAr" DA="mag$f" />
|
||||
|
||||
<Value name="三相总有功功率95值值" desc="总有功功率95值" type="6" DO="MMXU2$MX$TotW" DA="mag$f" />
|
||||
<Value name="三相总有功功率平均值值" desc="总有功功率平均值" type="6" DO="MMXU2$MX$TotW" DA="mag$f" />
|
||||
<Value name="三相总有功功率最大值值" desc="总有功功率最大值" type="6" DO="MMXU3$MX$TotW" DA="mag$f" />
|
||||
<Value name="三相总有功功率最小值值" desc="总有功功率最小值" type="6" DO="MMXU4$MX$TotW" DA="mag$f" />
|
||||
|
||||
<Value name="相电压总有效值实时数据值" desc="实时电压有效值" type="6" DO="MMXU$MX$PhV" DA="phs*$cVal$mag$f" Coefficient="1" />
|
||||
<Value name="相电压偏差实时数据值" desc="实时电压偏差" type="6" DO="MMXU$MX$VolDev" DA="phs*$cVal$mag$f" Coefficient="1" />
|
||||
<Value name="相电压谐波总畸变率实时数据值" desc="实时电压总谐波畸变率" type="6" DO="MHAI$MX$ThdPhV" DA="phs*$cVal$mag$f" Coefficient="1" />
|
||||
<Value name="相电压基波有效值实时数据角度" desc="实时基波电压相角" type="6" DO="MHAI$MX$FundPhVAng" DA="phs*$cVal$ang$f" Coefficient="1" />
|
||||
<Value name="线电压总有效值实时数据值" desc="实时线电压有效值" type="6" DO="MMXU$MX$LnPhV" DA="phs*$cVal$mag$f" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" />
|
||||
<Value name="相电压基波有效值实时数据值" desc="实时基波电压有效值" type="6" DO="MHAI$MX$FundPhV" DA="phs*$cVal$mag$f" Coefficient="1" />
|
||||
<Value name="相电压谐波含有率序列实时数据值" desc="实时2-50次谐波电压含有率" type="6" DO="MHAI$MX$HPhV" DA="phs*Har[%-2]$mag$f" Coefficient="1" Offset="0" />
|
||||
<Value name="间谐波电压有效值序列间谐波实时数据值" desc="实时1-50次间谐波电压含有率" type="6" DO="MHAI$MX$HPhV" DA="phs*Har[%-0]$mag$f" Coefficient="1" Offset="0" />
|
||||
<Value name="谐波电压有效值序列实时数据角度" desc="实时谐波电压相角(2-50)" type="6" DO="MHAI$MX$HPhV" DA="phs*Har[%-2]$ang$f" />
|
||||
<Value name="相电压谐波含有率序列实时数据角度" desc="实时谐波电压相角(2-50)" type="6" DO="MHAI$MX$HPhV" DA="phs*Har[%-2]$ang$f" />
|
||||
<Value name="线电压偏差实时数据值" desc="实时线电压偏差" type="6" DO="MMXU$MX$LnVolDev" DA="phs*$cVal$mag$f" Coefficient="1"/>
|
||||
<Value name="线电压总有效值实时数据值" desc="实时线电压有效值" type="6" DO="MMXU$MX$LnPhV" DA="phs*$cVal$mag$f" BaseFlag="1" LimitUp="0*%U" LimitDown="150*%U" Coefficient="1" />
|
||||
<Value name="线电压谐波总畸变率实时数据值" desc="实时线电压总谐波畸变率" type="6" DO="MHAI$MX$ThdLnPhV" DA="phs*$cVal$mag$f" Coefficient="1"/>
|
||||
<Value name="相电压基波有效值实时数据角度" desc="实时基波线电压相角" type="6" DO="MHAI$MX$FundLnPhVAng" DA="phs*$cVal$ang$f" Coefficient="1" />
|
||||
<Value name="线电压基波有效值实时数据值" desc="实时基波线电压有效值" type="6" DO="MHAI$MX$FundLnPhV" DA="phs*$cVal$mag$f" Coefficient="1"/>
|
||||
<Value name="线电压间谐波含有率序列间谐波实时数据值" desc="实时间谐波线电压含有率(f25-2475)" type="6" DO="MHAI$MX$HLnPhV" DA="phs*Har[%-0]$mag$f" Coefficient="1" Offset="0" />
|
||||
<Value name="线电压谐波含有率序列实时数据值" desc="实时谐波线电压含有率(2-50)" type="6" DO="MHAI$MX$HLnPhV" DA="phs*Har[%-2]$mag$f" Coefficient="1" Offset="0" />
|
||||
<Value name="线电压谐波含有率序列实时数据角度" desc="实时谐波线电压相角(2-50)" type="6" DO="MHAI$MX$HLnPhV" DA="phs*Har[%-2]$ang$f" />
|
||||
<Value name="正序负序和零序电压实时数据负序值" desc="实时负序电压" type="6" DO="MSQI$MX$SeqV" DA="c2$cVal$mag$f" Coefficient="1" />
|
||||
<Value name="电压负序不平衡度实时数据值" desc="实时负序电压不平衡" type="6" DO="MSQI$MX$ImbNgV" DA="mag$f" Coefficient="1" />
|
||||
<Value name="正序负序和零序电压实时数据零序值" desc="实时零序电压" type="6" DO="MSQI$MX$SeqV" DA="c3$cVal$mag$f" Coefficient="1" />
|
||||
<Value name="电压零序不平衡度实时数据值" desc="实时零序电压不平衡" type="6" DO="MSQI$MX$ImbZroV" DA="mag$f" Coefficient="1" />
|
||||
<Value name="频率实时数据值" desc="实时频率" type="6" DO="MMXU$MX$Hz" DA="mag$f" Coefficient="1" />
|
||||
<Value name="频率偏差实时数据值" desc="实时频率偏差" type="6" DO="MMXU$MX$FreqDev" DA="mag$f" Coefficient="1" />
|
||||
<Value name="正序负序和零序电压实时数据正序值" desc="实时正序电压" type="6" DO="MSQI$MX$SeqV" DA="c1$cVal$mag$f" Coefficient="1" />
|
||||
<Value name="电流总有效值实时数据值" desc="实时电流有效值" type="6" DO="MMXU$MX$A" DA="phs*$cVal$mag$f" Coefficient="1" />
|
||||
<Value name="电流基波有效值实时数据值" desc="实时基波电流有效值" type="6" DO="MHAI$MX$HA" DA="phs*Har[1]$mag$f" Coefficient="1" />
|
||||
<Value name="电流总谐波畸变率实时数据值" desc="实时电流总谐波畸变率" type="6" DO="MHAI$MX$ThdPhA" DA="phs*$cVal$mag$f" Coefficient="1" />
|
||||
<Value name="电流基波有效值实时数据角度" desc="实时基波电流相角" type="6" DO="MHAI$MX$FundPhAAng" DA="phs*$cVal$ang$f" Coefficient="1" />
|
||||
<Value name="谐波电流有效值序列实时数据值" desc="实时2-50次谐波电流幅值" type="6" DO="MHAI$MX$HA" DA="phs*Har[%-0]$mag$f" Coefficient="1" Offset="0" />
|
||||
<Value name="间谐波电流有效值序列间谐波实时数据值" desc="实时1-50次间谐波电流含有率" type="6" DO="MHAI$MX$HA" DA="phs*Har[%-0]$mag$f" Coefficient="1" Offset="0" />
|
||||
<Value name="正序负序和零序电流实时数据负序值" desc="实时负序电流" type="6" DO="MSQI$MX$SeqA" DA="c2$cVal$mag$f" Coefficient="1" />
|
||||
<Value name="电流负序不平衡度实时数据值" desc="实时负序电流不平衡" type="6" DO="MSQI$MX$ImbNgA" DA="mag$f" Coefficient="1" />
|
||||
<Value name="正序负序和零序电流实时数据零序值" desc="实时零序电流" type="6" DO="MSQI$MX$SeqA" DA="c3$cVal$mag$f" Coefficient="1" />
|
||||
<Value name="电流零序不平衡度实时数据值" desc="实时零序电流不平衡" type="6" DO="MSQI$MX$ImbZroA" DA="mag$f" Coefficient="1" />
|
||||
<Value name="正序负序和零序电流实时数据正序值" desc="实时正序电流" type="6" DO="MSQI$MX$SeqA" DA="c1$cVal$mag$f" Coefficient="1" />
|
||||
<Value name="功率因数实时数据值" desc="实时功率因数" type="6" DO="MMXU$MX$PF" DA="phs*$cVal$mag$f" Coefficient="1" />
|
||||
<Value name="位移功率因数实时数据值" desc="实时基波功率因数" type="6" DO="MHAI$MX$HSigPF" DA="phs*Har[1]$mag$f" Coefficient="1" />
|
||||
<Value name="基波有功功率实时数据值" desc="实时基波有功功率" type="6" DO="MHAI$MX$HW" DA="phs*Har[1]$mag$f" Coefficient="1" />
|
||||
<Value name="视在功率实时数据值" desc="实时视在功率" type="6" DO="MMXU$MX$VA" DA="phs*$cVal$mag$f" Coefficient="1" />
|
||||
<Value name="无功功率实时数据值" desc="实时无功功率" type="6" DO="MMXU$MX$VAr" DA="phs*$cVal$mag$f" Coefficient="1" />
|
||||
<Value name="有功功率实时数据值" desc="实时有功功率" type="6" DO="MMXU$MX$W" DA="phs*$cVal$mag$f" Coefficient="1" />
|
||||
<Value name="三相功率因数实时数据值" desc="实时总功率因数" type="6" DO="MMXU$MX$TotPF" DA="mag$f" Coefficient="1" />
|
||||
<Value name="三相位移功率因数实时数据值" desc="实时总基波功率因数" type="6" DO="MHAI$MX$HTotPF" DA="har[1]$mag$f" Coefficient="1" />
|
||||
<Value name="三相总视在功率实时数据值" desc="实时总视在功率" type="6" DO="MMXU$MX$TotVA" DA="mag$f" Coefficient="1" />
|
||||
<Value name="三相总无功功率实时数据值" desc="实时总无功功率" type="6" DO="MMXU$MX$TotVAr" DA="mag$f" Coefficient="1" />
|
||||
<Value name="三相总有功功率实时数据值" desc="实时总有功功率" type="6" DO="MMXU$MX$TotW" DA="mag$f" Coefficient="1" />
|
||||
<Value name="相电压短时闪变值波动闪变值值" desc="实时短时闪变" type="6" DO="MFLK$MX$PhPst" DA="phs*$cVal$mag$f" Coefficient="1" PltFlag="False" />
|
||||
<Value name="线电压短时闪变值波动闪变值值" desc="实时线电压短时闪变" type="6" DO="MFLK$MX$LnPhPst" DA="phs*$cVal$mag$f" Coefficient="1" PltFlag="False" />
|
||||
<Value name="相电压长时闪变值波动闪变值值" desc="实时长时闪变" type="6" DO="MFLK$MX$PhPlt" DA="phs*$cVal$mag$f" Coefficient="1" PltFlag="True" />
|
||||
<Value name="线电压长时闪变值波动闪变值值" desc="实时线电压长时闪变" type="6" DO="MFLK$MX$LnPhPlt" DA="phs*$cVal$mag$f" Coefficient="1" PltFlag="True" />
|
||||
<Value name="电压扰动事件特征幅值电压变动a值" desc="实时残余电压" type="6" DO="QVVR$MX$VVa" DA="mag$f" Coefficient="1" />
|
||||
<Value name="电压扰动事件持续时间电压变动a值" desc="实时持续时间" type="6" DO="QVVR$MX$VVaTm" DA="mag$f" Coefficient="1" />
|
||||
@@ -0,0 +1,261 @@
|
||||
package com.njcn.gather.icd.mapping.debug;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.njcn.gather.icd.mapping.component.IcdToXmlResponseConverter;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.IcdToXmlGenerateResult;
|
||||
import com.njcn.gather.icd.mapping.pojo.enums.GenerateStatus;
|
||||
import com.njcn.gather.icd.mapping.service.impl.IcdToXmlTaskAppService;
|
||||
import org.springframework.boot.Banner;
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* JSON 转 XML 本地调试入口。
|
||||
*
|
||||
* 使用方式:
|
||||
* 1. 先通过 GetIcdMmsJsonDebugRunner 获取 mappingJson,或使用已有的 JSON 文件。
|
||||
* 2. 修改 MMS_JSON_FILE_PATH 指向包含 mappingJson 的文件路径。
|
||||
* 3. 直接运行 main,查看生成的 XML 内容。
|
||||
* 4. 可选:设置 SAVE_XML_TO_DISK = true 将 XML 保存到磁盘。
|
||||
*/
|
||||
public class JsonToXmlDebugRunner {
|
||||
|
||||
/**
|
||||
* 包含 mappingJson 的文件路径。
|
||||
* 文件格式:纯文本,内容为 getIcdMmsJson 接口返回的 mappingJson 字段值。
|
||||
* 或者完整的 MappingTaskResponse JSON(会自动提取 mappingJson 字段)。
|
||||
*/
|
||||
private static final String MMS_JSON_FILE_PATH = "D:/temp/mms-output/mapping.json";
|
||||
|
||||
/** 是否输出格式化 XML,便于人工查看。 */
|
||||
private static final boolean PRETTY_XML = true;
|
||||
|
||||
/** 是否把生成的 XML 写入磁盘。 */
|
||||
private static final boolean SAVE_XML_TO_DISK = true;
|
||||
|
||||
/** saveXmlToDisk=true 时使用的输出文件路径。 */
|
||||
private static final String OUTPUT_XML_PATH = "D:/temp/mms-output/generated-config.xml";
|
||||
|
||||
/** 是否打印详细的转换过程日志。 */
|
||||
private static final boolean VERBOSE_LOG = true;
|
||||
|
||||
public static void main(String[] args) {
|
||||
try (ConfigurableApplicationContext context = new SpringApplicationBuilder(DebugApplication.class)
|
||||
.web(WebApplicationType.NONE)
|
||||
.bannerMode(Banner.Mode.OFF)
|
||||
.logStartupInfo(false)
|
||||
.run(args)) {
|
||||
|
||||
IcdToXmlTaskAppService icdToXmlTaskAppService = context.getBean(IcdToXmlTaskAppService.class);
|
||||
IcdToXmlResponseConverter responseConverter = context.getBean(IcdToXmlResponseConverter.class);
|
||||
ObjectMapper objectMapper = createObjectMapper();
|
||||
|
||||
if (VERBOSE_LOG) {
|
||||
System.out.println("========== JSON 转 XML 调试开始 ==========");
|
||||
System.out.println("输入文件: " + MMS_JSON_FILE_PATH);
|
||||
System.out.println("输出文件: " + (SAVE_XML_TO_DISK ? OUTPUT_XML_PATH : "(不保存)"));
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
// 读取 JSON 文件
|
||||
String jsonContent = readJsonFile();
|
||||
if (VERBOSE_LOG) {
|
||||
System.out.println("✓ JSON 文件读取成功,长度: " + jsonContent.length() + " 字符");
|
||||
}
|
||||
|
||||
// 提取 mappingJson(可能是完整响应或纯 mappingJson)
|
||||
String mappingJson = extractMappingJson(jsonContent, objectMapper);
|
||||
if (VERBOSE_LOG) {
|
||||
System.out.println("✓ MappingJSON 提取成功,长度: " + mappingJson.length() + " 字符");
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
// 调用服务生成 XML
|
||||
if (VERBOSE_LOG) {
|
||||
System.out.println("正在转换 JSON 为 XML...");
|
||||
}
|
||||
IcdToXmlGenerateResult result = icdToXmlTaskAppService.generateXmlFromJson(mappingJson);
|
||||
|
||||
// 输出结果
|
||||
printResult(result, objectMapper);
|
||||
|
||||
// 保存 XML 到磁盘
|
||||
if (SAVE_XML_TO_DISK && result.getStatus() == GenerateStatus.SUCCESS) {
|
||||
saveXmlToDisk(result.getXmlContent());
|
||||
}
|
||||
|
||||
if (VERBOSE_LOG) {
|
||||
System.out.println();
|
||||
System.out.println("========== JSON 转 XML 调试结束 ==========");
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("JSON 转 XML 调试失败:" + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 JSON 文件内容。
|
||||
*/
|
||||
private static String readJsonFile() {
|
||||
Path jsonPath = Paths.get(MMS_JSON_FILE_PATH);
|
||||
if (!Files.exists(jsonPath)) {
|
||||
throw new IllegalArgumentException("JSON 文件不存在:" + jsonPath.toAbsolutePath());
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] bytes = Files.readAllBytes(jsonPath);
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException("读取 JSON 文件失败:" + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 JSON 内容中提取 mappingJson。
|
||||
* 支持两种格式:
|
||||
* 1. 完整的 MappingTaskResponse JSON(包含 mappingJson 字段)
|
||||
* 2. 纯 mappingJson 字符串
|
||||
*/
|
||||
private static String extractMappingJson(String jsonContent, ObjectMapper objectMapper) {
|
||||
try {
|
||||
// 尝试解析为对象,看是否包含 mappingJson 字段
|
||||
if (jsonContent.trim().startsWith("{")) {
|
||||
java.util.Map<String, Object> jsonMap = objectMapper.readValue(
|
||||
jsonContent,
|
||||
java.util.Map.class
|
||||
);
|
||||
|
||||
if (jsonMap.containsKey("mappingJson")) {
|
||||
Object mappingJsonObj = jsonMap.get("mappingJson");
|
||||
if (mappingJsonObj != null) {
|
||||
return mappingJsonObj.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果不是对象或不包含 mappingJson,则假设整个内容就是 mappingJson
|
||||
return jsonContent;
|
||||
} catch (Exception ex) {
|
||||
// 如果解析失败,也假设整个内容就是 mappingJson
|
||||
return jsonContent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印转换结果。
|
||||
*/
|
||||
private static void printResult(IcdToXmlGenerateResult result, ObjectMapper objectMapper) {
|
||||
System.out.println();
|
||||
System.out.println("===== 转换结果 =====");
|
||||
System.out.println("状态: " + result.getStatus());
|
||||
System.out.println("消息: " + result.getMessage());
|
||||
|
||||
if (result.getProblems() != null && !result.getProblems().isEmpty()) {
|
||||
System.out.println("问题列表:");
|
||||
for (String problem : result.getProblems()) {
|
||||
System.out.println(" - " + problem);
|
||||
}
|
||||
}
|
||||
|
||||
// if (result.getStatus() == GenerateStatus.SUCCESS) {
|
||||
// System.out.println();
|
||||
// System.out.println("----- 生成的 XML 内容 -----");
|
||||
// String xmlContent = result.getMappingXml();
|
||||
//
|
||||
// if (PRETTY_XML) {
|
||||
// // 简单格式化:每个标签独占一行
|
||||
// xmlContent = formatXml(xmlContent);
|
||||
// }
|
||||
//
|
||||
// System.out.println(xmlContent);
|
||||
// System.out.println("--------------------------");
|
||||
// System.out.println();
|
||||
// System.out.println("XML 长度: " + result.getMappingXml().length() + " 字符");
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单的 XML 格式化。
|
||||
*/
|
||||
private static String formatXml(String xml) {
|
||||
if (xml == null || xml.isEmpty()) {
|
||||
return xml;
|
||||
}
|
||||
|
||||
// 在标签前后添加换行
|
||||
String formatted = xml.replaceAll(">", ">\n")
|
||||
.replaceAll("<", "\n<")
|
||||
.replaceAll("\n\n", "\n");
|
||||
|
||||
// 简单的缩进处理
|
||||
StringBuilder result = new StringBuilder();
|
||||
int indentLevel = 0;
|
||||
String[] lines = formatted.split("\n");
|
||||
|
||||
for (String line : lines) {
|
||||
line = line.trim();
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果是闭合标签,减少缩进
|
||||
if (line.startsWith("</") && !line.endsWith("/>")) {
|
||||
indentLevel = Math.max(0, indentLevel - 1);
|
||||
}
|
||||
|
||||
// 添加缩进
|
||||
for (int i = 0; i < indentLevel; i++) {
|
||||
result.append(" ");
|
||||
}
|
||||
result.append(line).append("\n");
|
||||
|
||||
// 如果是开始标签且不是自闭合,增加缩进
|
||||
if (line.startsWith("<") && !line.startsWith("</")
|
||||
&& !line.endsWith("/>") && !line.endsWith(">")) {
|
||||
indentLevel++;
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存 XML 到磁盘。
|
||||
*/
|
||||
private static void saveXmlToDisk(String xmlContent) {
|
||||
try {
|
||||
Path outputPath = Paths.get(OUTPUT_XML_PATH);
|
||||
|
||||
// 创建父目录
|
||||
if (outputPath.getParent() != null && !Files.exists(outputPath.getParent())) {
|
||||
Files.createDirectories(outputPath.getParent());
|
||||
}
|
||||
|
||||
Files.write(outputPath, xmlContent.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
System.out.println("✓ XML 已保存到: " + outputPath.toAbsolutePath());
|
||||
System.out.println(" 文件大小: " + Files.size(outputPath) + " 字节");
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("保存 XML 文件失败:" + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static ObjectMapper createObjectMapper() {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
@SpringBootApplication(scanBasePackages = "com.njcn.gather.icd.mapping")
|
||||
public static class DebugApplication {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user