洪圣文你是大傻逼

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

@@ -34,6 +34,17 @@ Java 源码位于 `src/main/java`,配置文件位于 `src/main/resources`My
## 代码风格与命名规范
保持现有 Java 风格4 空格缩进、UTF-8 文件编码、基础包名使用 `com.njcn.gather`。命名沿用分层后缀,如 `*Controller``*Service``*ServiceImpl``*Mapper``*Param``*PO``*VO`。优先复用现有 Lombok 注解,如 `@Data``@RequiredArgsConstructor``@Slf4j`。Mapper XML 文件名应与接口名保持一致。业务代码中,关键流程、分支判断、状态流转或容易误解的节点需要补充简洁的中文注释,但不要添加无信息量的注释。
## 数据与 SQL 约束
- 新增业务表的 DO 优先复用当前 `BaseDO` / 审计字段风格;除非表本身明确不需要逻辑删除,不要再引入另一套审计基类。
- 不要假设运行时存在自动数据库迁移;如果代码依赖新表、新字段或新索引,必须同步补齐对应 SQL 与文档说明。
- SQL 脚本应放在目标模块的 `src/main/resources/sql/...` 下,并保持可审阅、可单独执行、语义清晰。
- 变更缓存、日志、审计相关逻辑时,优先沿用现有机制,不要绕开现有登录上下文、缓存约定和审计字段填充方式。
## 注释与编码
- 新增或修改代码时,关键字段、关键分支、关键约束和非直观实现应补充简洁中文注释。
- 不要为了省事删除原有有效注释,也不要添加无信息量的注释。
- 写入中文内容时必须保持 UTF-8 编码,并自行检查中文显示是否正常;不要用“改成英文”规避乱码问题。
## 提交与合并请求规范
当前 `main` 分支尚无可参考的提交历史,仓库内也没有既有提交规范。建议使用“模块前缀 + 动词短句”的提交格式,例如 `user: 优化登录会话校验``system: 增加字典参数校验`。提交 PR 时应说明影响模块、配置或数据结构变更、人工验证步骤;若接口行为有变化,附上请求与响应示例。

View File

@@ -6,6 +6,7 @@ CN_Tool 是一个基于 Spring Boot 的多模块后端聚合工程,当前仓
- 系统字典、日志、系统配置、注册资源管理
- WebSocket / Netty 通信基础设施
- 激活码与许可证能力
- 波形文本解析与查看数据组装能力
## 当前真实模块
@@ -17,9 +18,10 @@ CN_Tool 是一个基于 Spring Boot 的多模块后端聚合工程,当前仓
- `detection`
- `tools`
其中 `tools` 当前仅保留
其中 `tools` 当前包含
- `activate-tool`
- `wave-tool`
## 启动入口
@@ -27,7 +29,7 @@ CN_Tool 是一个基于 Spring Boot 的多模块后端聚合工程,当前仓
- `entrance/src/main/java/com/njcn/gather/EntranceApplication.java`
`entrance` 模块聚合了 `system``user``detection``activate-tool`,是当前运行时主入口。
`entrance` 模块聚合了 `system``user``detection``activate-tool``wave-tool`,是当前运行时主入口。
## 技术基线
@@ -72,6 +74,8 @@ P0 已补齐基线文档,建议按以下顺序阅读:
- 当前以通信基础设施为主,包含 WebSocket / Netty 相关组件
- `tools/activate-tool`
- 负责激活码生成、激活码验证、许可证读取等能力
- `tools/wave-tool`
- 负责波形文本解析与查看数据组装能力
## 文档使用规则

View File

@@ -33,6 +33,11 @@
<artifactId>activate-tool</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.njcn.gather</groupId>
<artifactId>wave-tool</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<build>

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;
}