From 26392c604725850309ac199a1b47a0342c1eee7a Mon Sep 17 00:00:00 2001 From: wanxiaoli Date: Fri, 9 Jan 2026 17:02:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=87=E4=BB=B6=E8=BD=AC?= =?UTF-8?q?=E6=8D=A2=E5=8F=AF=E9=85=8D=E7=BD=AE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/InsConvertConfig.java | 10 + .../experimentalData/config/OutputConfig.java | 13 + .../experimentalData/config/RuleConfig.java | 11 + .../service/InsFileConvertNewService.java | 274 ++++++++++++++++++ 4 files changed, 308 insertions(+) create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/config/InsConvertConfig.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/config/OutputConfig.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/config/RuleConfig.java create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/service/InsFileConvertNewService.java diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/config/InsConvertConfig.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/config/InsConvertConfig.java new file mode 100644 index 0000000..b98b4de --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/config/InsConvertConfig.java @@ -0,0 +1,10 @@ +package com.yfd.platform.modules.experimentalData.config; + +import lombok.Data; +import java.util.List; + +@Data +public class InsConvertConfig { + private List outputs; +} + diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/config/OutputConfig.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/config/OutputConfig.java new file mode 100644 index 0000000..560f17a --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/config/OutputConfig.java @@ -0,0 +1,13 @@ +package com.yfd.platform.modules.experimentalData.config; + +import lombok.Data; +import java.util.Map; + +@Data +public class OutputConfig { + private String name; // 文件标识,如 vins/fvns + private String templateResource; // 模板路径 classpath + private String delimiter = ","; // 分隔符,默认逗号 + private String extension = ".txt"; // 文件扩展名,默认 .txt + private Map rules; // 输出列规则 +} diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/config/RuleConfig.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/config/RuleConfig.java new file mode 100644 index 0000000..a715629 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/config/RuleConfig.java @@ -0,0 +1,11 @@ +package com.yfd.platform.modules.experimentalData.config; + +import lombok.Data; + +@Data +public class RuleConfig { + private String type; // INPUT / CONST + private String from; // INPUT 类型对应 INS 列 + private String value; // CONST 类型固定值 +} + diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/InsFileConvertNewService.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/InsFileConvertNewService.java new file mode 100644 index 0000000..457260a --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/InsFileConvertNewService.java @@ -0,0 +1,274 @@ +package com.yfd.platform.modules.experimentalData.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.yfd.platform.modules.experimentalData.config.InsConvertConfig; +import com.yfd.platform.modules.experimentalData.config.OutputConfig; +import com.yfd.platform.modules.experimentalData.config.RuleConfig; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +/** + * INS 文件转换服务 + * - JSON 配置支持 INPUT / CONST 列 + * - 模板文件决定输出列全集 + * - 支持默认配置 + 客户自定义配置 + */ +@Service +@Slf4j +public class InsFileConvertNewService { + + private static final Charset UTF8 = StandardCharsets.UTF_8; + + /** + * 转换 INS 文件 + * @param insFile INS 源文件 + * @param jsonConfigFile 客户自定义 JSON 配置(可为 null,使用默认) + * @return Map<输出文件标识, 输出文件对象> + */ + public Map convert(File insFile, File jsonConfigFile) throws Exception { + + long start = System.currentTimeMillis(); + + if (!insFile.exists()) { + throw new IOException("INS 文件不存在:" + insFile.getAbsolutePath()); + } + + // 1. 加载配置 + InsConvertConfig config = loadConfig(jsonConfigFile); + + // 2. 读取 INS 表头 + String insDelimiter; + Map insHeaderIndex; + try (BufferedReader insReader = Files.newBufferedReader(insFile.toPath(), UTF8)) { + String insHeaderLine = insReader.readLine(); + if (insHeaderLine == null) { + throw new IllegalStateException("INS 文件为空"); + } + //---------- 自动识别分隔符 ---------- + + if (insHeaderLine.contains("\t")) { + insDelimiter = "\t"; + } else if (insHeaderLine.contains(",")) { + insDelimiter = ","; + } else { + throw new RuntimeException("无法识别 INS 文件的分隔符"); + } + + // 构建列名索引 + String[] insHeaders = insHeaderLine.split(insDelimiter, -1); + Map insIndex = new HashMap<>(); + for (int i = 0; i < insHeaders.length; i++) { + insIndex.put(insHeaders[i].trim(), i); + } + insHeaderIndex = parseHeaderIndex(insHeaderLine,insDelimiter); + } + + // 3. 校验 INS 必需字段 + validateInsHeader(insHeaderIndex, config); + + // 4. 初始化每个输出文件 + Map outputs = initOutputs(config.getOutputs(), insFile.getParentFile()); + + // 5. 流式读取 INS 正文 + long rowCount = 0; + try (BufferedReader insReader = Files.newBufferedReader(insFile.toPath(), UTF8)) { + insReader.readLine(); // skip header + String line; + while ((line = insReader.readLine()) != null) { + rowCount++; + String[] insValues = line.split(insDelimiter, -1); + + for (OutputContext ctx : outputs.values()) { + String[] out = new String[ctx.headers.length]; + Arrays.fill(out, "0"); + + for (Map.Entry e : ctx.rules.entrySet()) { + String outCol = e.getKey(); + RuleConfig rule = e.getValue(); + int outIdx = ctx.headerIndex.get(outCol); + + if ("INPUT".equalsIgnoreCase(rule.getType())) { + int inIdx = insHeaderIndex.get(rule.getFrom()); + out[outIdx] = inIdx < insValues.length ? insValues[inIdx] : "0"; + } else if ("CONST".equalsIgnoreCase(rule.getType())) { + out[outIdx] = rule.getValue() != null ? rule.getValue() : "0"; + } else { + out[outIdx] = "0"; // 未来扩展 FORMULA + } + } + + ctx.writer.write(String.join(ctx.delimiter, out)); + ctx.writer.newLine(); + } + } + } + + // 6. 关闭 writer + for (OutputContext ctx : outputs.values()) { + ctx.writer.flush(); + ctx.writer.close(); + } + + log.info("INS 转换完成,行数={}, 耗时={}ms", rowCount, System.currentTimeMillis() - start); + + Map result = new HashMap<>(); + for (OutputContext ctx : outputs.values()) { + result.put(ctx.name, ctx.outFile); + } + return result; + } + + // =================== 内部方法 =================== + + /** 加载 JSON 配置,客户自定义优先,否则默认 */ + private InsConvertConfig loadConfig(File jsonConfigFile) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + if (jsonConfigFile != null) { + log.info("加载客户自定义 JSON 配置:{}", jsonConfigFile.getAbsolutePath()); + return mapper.readValue(jsonConfigFile, InsConvertConfig.class); + } else { + log.info("加载默认 JSON 配置:config/ins-convert-default.json"); + ClassPathResource res = new ClassPathResource("config/ins-convert-default.json"); + try (InputStreamReader in = new InputStreamReader(res.getInputStream(), UTF8)) { + return mapper.readValue(in, InsConvertConfig.class); + } + } + } + + /** 解析表头到 Map<列名,索引> */ + private Map parseHeaderIndex(String headerLine,String delimiter) { + String[] headers = headerLine.split(delimiter, -1); + Map map = new HashMap<>(); + for (int i = 0; i < headers.length; i++) { + map.put(headers[i].trim(), i); + } + return map; + } + + /** 用","解析输出表头到 Map<列名,索引> */ + private Map parseOutHeaderIndex(String headerLine,String delimiter) { + String[] headers = headerLine.split(delimiter, -1); + Map map = new HashMap<>(); + for (int i = 0; i < headers.length; i++) { + map.put(headers[i].trim(), i); + } + return map; + } + + /** 校验 INS 必需列 */ + private void validateInsHeader(Map insHeader, InsConvertConfig config) { + Set required = new HashSet<>(); + for (OutputConfig out : config.getOutputs()) { + for (RuleConfig rule : out.getRules().values()) { + if ("INPUT".equalsIgnoreCase(rule.getType())) { + required.add(rule.getFrom()); + } + } + } + + for (String col : required) { + if (!insHeader.containsKey(col)) { + throw new IllegalArgumentException("INS 缺少必需字段:" + col); + } + } + } + + /** 初始化输出文件流和上下文 */ + private Map initOutputs( + List outputs, File parentDir) throws Exception { + + Map map = new HashMap<>(); + for (OutputConfig out : outputs) { + + // 1.读取模板文件表头 + ClassPathResource tpl = new ClassPathResource(out.getTemplateResource()); + String headerLine; + try (BufferedReader r = new BufferedReader(new InputStreamReader(tpl.getInputStream(), UTF8))) { + headerLine = r.readLine(); + } + + if (headerLine == null || headerLine.isEmpty()) { + throw new RuntimeException("模板文件为空:" + out.getTemplateResource()); + } + + String delimiter = out.getDelimiter() != null ? out.getDelimiter() : ","; + String[] headers = headerLine.split(delimiter, -1); + Map headerIndex = parseOutHeaderIndex(headerLine,delimiter); + + // ---------- 2. 输出规则列校验 ---------- + for (String col : out.getRules().keySet()) { + if (!headerIndex.containsKey(col)) { + throw new IllegalStateException( + "规则列不存在于模板:" + col + " 模板文件:" + out.getTemplateResource()); + } + } + + // --------- 3. 构造输出文件 --------- + String ts = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + String ext = out.getExtension() != null ? out.getExtension() : ".txt"; + File outFile = new File(parentDir, out.getName() + "_" + ts + ext); + + BufferedWriter writer = Files.newBufferedWriter(outFile.toPath(), UTF8); + + // ------- 4. 写入模板表头 ---------- + writer.write(headerLine); + writer.newLine(); + + // ---------- 5. 初始化上下文 ---------- + OutputContext ctx = new OutputContext(); + ctx.name = out.getName(); + ctx.headers = headers; + ctx.headerIndex = headerIndex; + ctx.rules = out.getRules(); + ctx.writer = writer; + ctx.delimiter = out.getDelimiter(); + ctx.outFile = outFile; + + map.put(ctx.name, ctx); + } + return map; + } + + /** 输出上下文 */ + private static class OutputContext { + String name; + String[] headers; + Map rules; + Map headerIndex; + BufferedWriter writer; + String delimiter; + File outFile; + } + + // =================== 测试 main =================== + public static void main(String[] args) { + try { + InsFileConvertNewService service = new InsFileConvertNewService(); + + File insFile = new File("D:/data/ins_frameSimu_0.txt"); // 修改为实际路径 + File jsonFile = new File("D:/data/ins-convert-2.json"); + + // File jsonFile = new File("D:/data/customer-ins-convert.json"); // 客户自定义 JSON + Map result = service.convert(insFile,jsonFile); // 使用默认配置 + + result.forEach((k, f) -> System.out.println(k + " -> " + f.getAbsolutePath())); + System.out.println("转换完成!"); + } catch (Exception e) { + e.printStackTrace(); + } + } +}