添加文件转换可配置功能
This commit is contained in:
parent
a4a0cdbf1d
commit
26392c6047
@ -0,0 +1,10 @@
|
||||
package com.yfd.platform.modules.experimentalData.config;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class InsConvertConfig {
|
||||
private List<OutputConfig> outputs;
|
||||
}
|
||||
|
||||
@ -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<String, RuleConfig> rules; // 输出列规则
|
||||
}
|
||||
@ -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 类型固定值
|
||||
}
|
||||
|
||||
@ -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<String, File> 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<String, Integer> 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<String, Integer> 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<String, OutputContext> 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<String, RuleConfig> 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<String, File> 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<String, Integer> parseHeaderIndex(String headerLine,String delimiter) {
|
||||
String[] headers = headerLine.split(delimiter, -1);
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
map.put(headers[i].trim(), i);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/** 用","解析输出表头到 Map<列名,索引> */
|
||||
private Map<String, Integer> parseOutHeaderIndex(String headerLine,String delimiter) {
|
||||
String[] headers = headerLine.split(delimiter, -1);
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
map.put(headers[i].trim(), i);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/** 校验 INS 必需列 */
|
||||
private void validateInsHeader(Map<String, Integer> insHeader, InsConvertConfig config) {
|
||||
Set<String> 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<String, OutputContext> initOutputs(
|
||||
List<OutputConfig> outputs, File parentDir) throws Exception {
|
||||
|
||||
Map<String, OutputContext> 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<String, Integer> 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<String, RuleConfig> rules;
|
||||
Map<String, Integer> 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<String, File> result = service.convert(insFile,jsonFile); // 使用默认配置
|
||||
|
||||
result.forEach((k, f) -> System.out.println(k + " -> " + f.getAbsolutePath()));
|
||||
System.out.println("转换完成!");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user