洪圣文你是大傻逼

This commit is contained in:
2026-04-15 11:48:33 +08:00
parent 4f9124d56a
commit a9432d4534
12 changed files with 445 additions and 8 deletions

View File

@@ -4,17 +4,19 @@
`tools` 当前是工具能力聚合模块,但在本仓库内已经完成一次收口。
当前真实保留的子模块有:
当前真实保留的子模块有:
- `activate-tool`
- `wave-tool`
因此,`tools` 现阶段不是一个包含多个通用工具的完整工具市场,而是一个仅保留激活能力的聚合模块。
因此,`tools` 现阶段仍然是聚合模块,但当前已实际承载激活工具和波形查看工具两个子模块。
## 当前结构
```text
tools/
── activate-tool/
── activate-tool/
└── wave-tool/
```
## activate-tool 的职责
@@ -28,6 +30,17 @@ tools/
从接口层看,当前主要围绕 `/activate/*` 路径提供能力。
## wave-tool 的职责
`wave-tool` 当前提供的能力主要围绕波形文本解析与查看数据组装:
- 解析单列幅值波形文本
- 解析双列时间/幅值波形文本
- 统计点位范围、均值、点数等摘要信息
- 按查看场景输出下采样后的点位集合
从接口层看,当前主要围绕 `/wave/*` 路径提供能力。
## 模块定位
当前 `activate-tool` 更适合作为平台级基础能力模块,而不是业务检测模块的一部分。
@@ -40,7 +53,7 @@ tools/
## 依赖关系
`tools/activate-tool` 当前主要依赖:
`tools/activate-tool``tools/wave-tool` 当前主要依赖:
- `com.njcn:njcn-common`
- `com.njcn:spingboot2.3.12`

View File

@@ -16,9 +16,10 @@
<description>Retained utility aggregator for platform capabilities.</description>
<modules>
<!-- Key refactor point: business-side tool modules are removed, and only
the activation capability is retained in the tools aggregator. -->
<!-- Retain platform tools in the aggregator and add dedicated utility modules
here when they are actually introduced into this repository. -->
<module>activate-tool</module>
<module>wave-tool</module>
</modules>
</project>

28
tools/wave-tool/pom.xml Normal file
View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.njcn.gather</groupId>
<artifactId>tools</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>wave-tool</artifactId>
<dependencies>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>njcn-common</artifactId>
<version>0.0.1</version>
</dependency>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>spingboot2.3.12</artifactId>
<version>2.3.12</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,42 @@
package com.njcn.gather.tool.wave.controller;
import com.njcn.common.pojo.annotation.OperateInfo;
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.tool.wave.param.WaveParseParam;
import com.njcn.gather.tool.wave.service.WaveService;
import com.njcn.gather.tool.wave.vo.WaveParseResultVO;
import com.njcn.web.controller.BaseController;
import com.njcn.web.utils.HttpResultUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.RestController;
@Slf4j
@Api(tags = "波形查看")
@RestController
@RequestMapping("/wave")
@RequiredArgsConstructor
public class WaveController extends BaseController {
private final WaveService waveService;
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("解析波形文本")
@ApiImplicitParam(name = "param", value = "波形解析参数", required = true, dataType = "WaveParseParam")
@PostMapping("/parse")
public HttpResult<WaveParseResultVO> parse(@RequestBody WaveParseParam param) {
String methodDescribe = getMethodDescribe("parse");
LogUtil.njcnDebug(log, "{},开始解析波形文本", methodDescribe);
WaveParseResultVO result = waveService.parse(param);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
}

View File

@@ -0,0 +1,36 @@
package com.njcn.gather.tool.wave.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
@Data
@ApiModel("波形解析参数")
public class WaveParseParam {
@ApiModelProperty(value = "波形文本内容,支持单列幅值、单行多值或双列时间/幅值数据", required = true)
private String waveformText;
@ApiModelProperty(value = "分隔符,默认 AUTO 自动识别,支持直接传入具体字符,也支持 TAB 或 SPACE")
private String separator;
@ApiModelProperty(value = "是否包含 X 轴列true 表示文本中显式传入时间列")
private Boolean containsXAxis;
@ApiModelProperty(value = "X 轴列下标,默认 0")
private Integer xColumnIndex;
@ApiModelProperty(value = "Y 轴列下标,单列波形默认 0双列波形默认 1")
private Integer yColumnIndex;
@ApiModelProperty(value = "跳过的表头行数,默认 0")
private Integer skipHeaderLines;
@ApiModelProperty(value = "单列波形的采样间隔,默认 1")
private BigDecimal samplingInterval;
@ApiModelProperty(value = "返回的最大点位数,超过时自动下采样,默认 2000")
private Integer maxPointCount;
}

View File

@@ -0,0 +1,15 @@
package com.njcn.gather.tool.wave.service;
import com.njcn.gather.tool.wave.param.WaveParseParam;
import com.njcn.gather.tool.wave.vo.WaveParseResultVO;
public interface WaveService {
/**
* 解析波形文本并输出适合查看的点位结果。
*
* @param param 波形解析参数
* @return 波形查看结果
*/
WaveParseResultVO parse(WaveParseParam param);
}

View File

@@ -0,0 +1,214 @@
package com.njcn.gather.tool.wave.service.impl;
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.tool.wave.param.WaveParseParam;
import com.njcn.gather.tool.wave.service.WaveService;
import com.njcn.gather.tool.wave.vo.WaveParseResultVO;
import com.njcn.gather.tool.wave.vo.WavePointVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
@Slf4j
@Service
public class WaveServiceImpl implements WaveService {
private static final int DEFAULT_MAX_POINT_COUNT = 2000;
private static final String AUTO_SEPARATOR = "AUTO";
@Override
public WaveParseResultVO parse(WaveParseParam param) {
if (param == null || StrUtil.isBlank(param.getWaveformText())) {
throw new BusinessException(CommonResponseEnum.FAIL, "波形文本不能为空");
}
boolean containsXAxis = Boolean.TRUE.equals(param.getContainsXAxis());
int skipHeaderLines = sanitizeSkipHeaderLines(param.getSkipHeaderLines());
int maxPointCount = sanitizeMaxPointCount(param.getMaxPointCount());
BigDecimal samplingInterval = sanitizeSamplingInterval(param.getSamplingInterval());
int xColumnIndex = sanitizeColumnIndex(param.getXColumnIndex(), 0);
int yColumnIndex = sanitizeColumnIndex(param.getYColumnIndex(), containsXAxis ? 1 : 0);
List<WavePointVO> sourcePoints = new ArrayList<>();
int ignoredLineCount = 0;
int nonBlankLineIndex = 0;
String[] lines = param.getWaveformText().split("\\r?\\n");
for (String line : lines) {
if (StrUtil.isBlank(line)) {
continue;
}
if (nonBlankLineIndex++ < skipHeaderLines) {
continue;
}
String[] columns = splitColumns(line, param.getSeparator());
if (columns.length == 0) {
ignoredLineCount++;
continue;
}
try {
if (containsXAxis) {
WavePointVO point = buildPoint(columns, xColumnIndex, yColumnIndex);
sourcePoints.add(point);
} else {
sourcePoints.addAll(buildSingleColumnPoints(columns, samplingInterval, sourcePoints.size()));
}
} catch (Exception ex) {
ignoredLineCount++;
log.debug("波形行解析失败line={}, reason={}", line, ex.getMessage());
}
}
if (sourcePoints.isEmpty()) {
throw new BusinessException(CommonResponseEnum.FAIL, "未解析到有效波形点位");
}
List<WavePointVO> displayPoints = downSample(sourcePoints, maxPointCount);
return buildResult(sourcePoints, displayPoints, ignoredLineCount, containsXAxis);
}
private WavePointVO buildPoint(String[] columns, int xColumnIndex, int yColumnIndex) {
BigDecimal yValue = parseNumber(readColumn(columns, yColumnIndex));
BigDecimal xValue = parseNumber(readColumn(columns, xColumnIndex));
return new WavePointVO(xValue, yValue);
}
private List<WavePointVO> buildSingleColumnPoints(String[] columns, BigDecimal samplingInterval, int startIndex) {
List<WavePointVO> points = new ArrayList<>();
for (int i = 0; i < columns.length; i++) {
BigDecimal yValue = parseNumber(columns[i]);
// 单列波形默认按采样间隔自动补齐 X 轴,便于前端直接绘制。
BigDecimal xValue = samplingInterval.multiply(BigDecimal.valueOf(startIndex + i));
points.add(new WavePointVO(xValue, yValue));
}
return points;
}
private String readColumn(String[] columns, int columnIndex) {
if (columnIndex < 0 || columnIndex >= columns.length) {
throw new IllegalArgumentException("列下标超出范围");
}
return columns[columnIndex];
}
private BigDecimal parseNumber(String value) {
if (StrUtil.isBlank(value)) {
throw new IllegalArgumentException("数值为空");
}
return new BigDecimal(value.trim());
}
private String[] splitColumns(String line, String separator) {
String trimmedLine = line.trim();
if (StrUtil.isBlank(trimmedLine)) {
return new String[0];
}
String[] parts;
if (StrUtil.isBlank(separator) || AUTO_SEPARATOR.equalsIgnoreCase(separator)) {
parts = trimmedLine.split("[,;\\s]+");
} else if ("TAB".equalsIgnoreCase(separator)) {
parts = trimmedLine.split("\\t+");
} else if ("SPACE".equalsIgnoreCase(separator)) {
parts = trimmedLine.split("\\s+");
} else {
parts = trimmedLine.split(Pattern.quote(separator));
}
return Arrays.stream(parts)
.map(String::trim)
.filter(StrUtil::isNotBlank)
.toArray(String[]::new);
}
private List<WavePointVO> downSample(List<WavePointVO> sourcePoints, int maxPointCount) {
if (sourcePoints.size() <= maxPointCount) {
return sourcePoints;
}
List<WavePointVO> result = new ArrayList<>();
int step = (int) Math.ceil((double) sourcePoints.size() / maxPointCount);
for (int i = 0; i < sourcePoints.size(); i += step) {
result.add(sourcePoints.get(i));
}
WavePointVO lastPoint = sourcePoints.get(sourcePoints.size() - 1);
if (!result.contains(lastPoint) && result.size() < maxPointCount) {
result.add(lastPoint);
} else if (!result.isEmpty()) {
result.set(result.size() - 1, lastPoint);
}
return result;
}
private WaveParseResultVO buildResult(List<WavePointVO> sourcePoints, List<WavePointVO> displayPoints,
int ignoredLineCount, boolean containsXAxis) {
BigDecimal minX = sourcePoints.get(0).getX();
BigDecimal maxX = sourcePoints.get(0).getX();
BigDecimal minY = sourcePoints.get(0).getY();
BigDecimal maxY = sourcePoints.get(0).getY();
BigDecimal sumY = BigDecimal.ZERO;
for (WavePointVO point : sourcePoints) {
if (point.getX().compareTo(minX) < 0) {
minX = point.getX();
}
if (point.getX().compareTo(maxX) > 0) {
maxX = point.getX();
}
if (point.getY().compareTo(minY) < 0) {
minY = point.getY();
}
if (point.getY().compareTo(maxY) > 0) {
maxY = point.getY();
}
sumY = sumY.add(point.getY());
}
WaveParseResultVO result = new WaveParseResultVO();
result.setContainsXAxis(containsXAxis);
result.setSourcePointCount(sourcePoints.size());
result.setDisplayPointCount(displayPoints.size());
result.setIgnoredLineCount(ignoredLineCount);
result.setSampled(sourcePoints.size() != displayPoints.size());
result.setMinX(minX);
result.setMaxX(maxX);
result.setMinY(minY);
result.setMaxY(maxY);
result.setAverageY(sumY.divide(BigDecimal.valueOf(sourcePoints.size()), 6, RoundingMode.HALF_UP));
result.setPoints(displayPoints);
return result;
}
private int sanitizeSkipHeaderLines(Integer skipHeaderLines) {
if (skipHeaderLines == null || skipHeaderLines < 0) {
return 0;
}
return skipHeaderLines;
}
private int sanitizeMaxPointCount(Integer maxPointCount) {
if (maxPointCount == null || maxPointCount <= 0) {
return DEFAULT_MAX_POINT_COUNT;
}
return maxPointCount;
}
private int sanitizeColumnIndex(Integer columnIndex, int defaultValue) {
if (columnIndex == null || columnIndex < 0) {
return defaultValue;
}
return columnIndex;
}
private BigDecimal sanitizeSamplingInterval(BigDecimal samplingInterval) {
if (samplingInterval == null || samplingInterval.compareTo(BigDecimal.ZERO) <= 0) {
return BigDecimal.ONE;
}
return samplingInterval;
}
}

View File

@@ -0,0 +1,46 @@
package com.njcn.gather.tool.wave.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
@ApiModel("波形解析结果")
public class WaveParseResultVO {
@ApiModelProperty("是否包含显式 X 轴")
private Boolean containsXAxis;
@ApiModelProperty("原始有效点位数")
private Integer sourcePointCount;
@ApiModelProperty("返回的显示点位数")
private Integer displayPointCount;
@ApiModelProperty("被忽略的无效行数")
private Integer ignoredLineCount;
@ApiModelProperty("是否发生下采样")
private Boolean sampled;
@ApiModelProperty("X 轴最小值")
private BigDecimal minX;
@ApiModelProperty("X 轴最大值")
private BigDecimal maxX;
@ApiModelProperty("Y 轴最小值")
private BigDecimal minY;
@ApiModelProperty("Y 轴最大值")
private BigDecimal maxY;
@ApiModelProperty("Y 轴平均值")
private BigDecimal averageY;
@ApiModelProperty("用于查看的波形点位")
private List<WavePointVO> points;
}

View File

@@ -0,0 +1,22 @@
package com.njcn.gather.tool.wave.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("波形点位")
public class WavePointVO {
@ApiModelProperty("X 轴值")
private BigDecimal x;
@ApiModelProperty("Y 轴值")
private BigDecimal y;
}