添加数据处理功能,试验任务导入功能优化

This commit is contained in:
wanxiaoli 2026-01-07 16:25:57 +08:00
parent 9e889790fd
commit 6da5da86af
5 changed files with 255 additions and 4 deletions

View File

@ -0,0 +1,51 @@
package com.yfd.platform.modules.experimentalData.controller;
import com.yfd.platform.modules.experimentalData.service.InsMergeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
@RestController
@RequestMapping("/api/ins")
public class InsMergeController {
@Autowired
private InsMergeService service;
@PostMapping("/merge")
public ResponseEntity<FileSystemResource> merge(
@RequestParam MultipartFile vinsFile,
@RequestParam MultipartFile fvnsFile,
@RequestParam(required = false) MultipartFile templateFile
) throws Exception {
// 1 必传文件
File vins = File.createTempFile("vins", ".txt");
File fvns = File.createTempFile("fvns", ".csv");
vinsFile.transferTo(vins);
fvnsFile.transferTo(fvns);
// 2 可选模板文件
File tmpl = null;
if (templateFile != null && !templateFile.isEmpty()) {
tmpl = File.createTempFile("tmpl", ".txt");
templateFile.transferTo(tmpl);
}
// 3 合并处理Service 内决定使用默认模板还是自定义模板
File out = service.merge(vins, fvns, tmpl);
// 4 下载响应
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=ins_frameSimu_0_out.txt")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(out.length())
.body(new FileSystemResource(out));
}
}

View File

@ -0,0 +1,154 @@
package com.yfd.platform.modules.experimentalData.service;
import com.yfd.platform.utils.InterpCursor;
import org.springframework.stereotype.Service;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
@Service
public class InsMergeService {
public File merge(File vinsFile, File fvnsFile, File templateFileOptional) throws Exception {
// ---------- 1. 模板表头 ----------
String[] headers = loadTemplateHeaders(templateFileOptional);
// try (BufferedReader br = new BufferedReader(new FileReader(templateFile))) {
// headers = br.readLine().split("\t");
// }
// ---------- 2. 读取 FVNS一次性较小 ----------
List<Double> tList = new ArrayList<>();
List<Double> sList = new ArrayList<>();
List<Double> xList = new ArrayList<>();
List<Double> yList = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new FileReader(fvnsFile))) {
String[] head = br.readLine().split(",", -1);
Map<String, Integer> idx = indexMap(head);
String line;
while ((line = br.readLine()) != null) {
String[] a = line.split(",",-1);
tList.add(Double.parseDouble(a[idx.get("UTC")]));
sList.add(Double.parseDouble(a[idx.get("FvnsSts")]));
xList.add(Double.parseDouble(a[idx.get("PosxFvns")]));
yList.add(Double.parseDouble(a[idx.get("PosyFvns")]));
}
}
// 检查时间序列,在读 FVNS 只加一次校验即可
for (int i = 1; i < tList.size(); i++) {
if (tList.get(i) < tList.get(i - 1)) {
throw new IllegalStateException("FVNS UTC not sorted at line " + i);
}
}
InterpCursor cPosX = new InterpCursor(toArr(tList), toArr(sList));
InterpCursor cPosY = new InterpCursor(toArr(tList), toArr(xList));
InterpCursor cPosZ = new InterpCursor(toArr(tList), toArr(yList));
// ---------- 3. 输出文件 ----------
File out = File.createTempFile("ins_frameSimu_0_out", ".txt");
try (
BufferedReader br = new BufferedReader(new FileReader(vinsFile));
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(out), StandardCharsets.UTF_8), 1 << 20)
) {
// VINS header
String[] vinsHead = br.readLine().split(",",-1);
Map<String, Integer> vinsIdx = indexMap(vinsHead);
requireField(vinsIdx, "UTC");
requireField(vinsIdx, "LatGps");
requireField(vinsIdx, "LonGps");
// 写表头
bw.write(String.join("\t", headers));
bw.newLine();
String line;
while ((line = br.readLine()) != null) {
String[] a = line.split(",",-1);
double utc = Double.parseDouble(a[vinsIdx.get("UTC")]);
double lat = Double.parseDouble(a[vinsIdx.get("LatGps")]);
double lon = Double.parseDouble(a[vinsIdx.get("LonGps")]);
double posx = cPosX.valueAt(utc);
double posy = cPosY.valueAt(utc);
double posz = cPosZ.valueAt(utc);
// 按模板顺序输出
for (int i = 0; i < headers.length; i++) {
switch (headers[i]) {
case "utc": bw.write(fmt(utc)); break;
case "LatJG": bw.write(fmt(lat)); break;
case "LonJG": bw.write(fmt(lon)); break;
case "posx": bw.write(fmt(posx)); break;
case "posy": bw.write(fmt(posy)); break;
case "posz": bw.write(fmt(posz)); break;
default: bw.write("0"); break;
}
if (i < headers.length - 1) bw.write("\t");
}
bw.newLine();
}
}
return out;
}
// ---------- utils ----------
private Map<String, Integer> indexMap(String[] head) {
Map<String, Integer> m = new HashMap<>();
for (int i = 0; i < head.length; i++) {
m.put(head[i].trim(), i);
}
return m;
}
private void requireField(Map<String, Integer> idx, String name) {
if (!idx.containsKey(name)) {
throw new IllegalArgumentException("Missing required field: " + name);
}
}
private double[] toArr(List<Double> list) {
double[] a = new double[list.size()];
for (int i = 0; i < a.length; i++) a[i] = list.get(i);
return a;
}
private String fmt(double v) {
return String.format(Locale.US, "%.6f", v);
}
private String[] loadTemplateHeaders(File templateFile) throws IOException {
// 1 若用户上传模板
if (templateFile != null && templateFile.exists()) {
try (BufferedReader br = new BufferedReader(new FileReader(templateFile))) {
return br.readLine().split("\t");
}
}
// 2 使用内置模板
try (InputStream is = getClass()
.getClassLoader()
.getResourceAsStream("templates/ins_frameSimu_0.txt")) {
if (is == null) {
throw new RuntimeException("Default template ins_frameSimu_0.txt not found");
}
try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
return br.readLine().split("\t");
}
}
}
}

View File

@ -1240,6 +1240,7 @@ public class TsTaskServiceImpl extends ServiceImpl<TsTaskMapper, TsTask> impleme
@Transactional(rollbackFor = Exception.class)
public boolean executeSqlImport(List<String> sqlStatements) {
int total = 0; // 在方法开始处声明 total 变量
int failedCount = 0; // 记录失败的SQL数量
try {
jdbcTemplate.execute("SET FOREIGN_KEY_CHECKS=0;");
jdbcTemplate.execute("SET UNIQUE_CHECKS=0;");
@ -1252,14 +1253,16 @@ public class TsTaskServiceImpl extends ServiceImpl<TsTaskMapper, TsTask> impleme
if (StringUtils.isBlank(sql)) continue;
batch.add(sql);
if (batch.size() >= batchSize) {
batchExecute(batch);
int failedInBatch = batchExecute(batch);
failedCount += failedInBatch;
total += batch.size();
LOGGER.info("已执行 {} 条SQL语句", total);
batch.clear();
}
}
if (!batch.isEmpty()) {
batchExecute(batch);
int failedInBatch = batchExecute(batch);
failedCount += failedInBatch;
total += batch.size();
}
@ -1267,7 +1270,14 @@ public class TsTaskServiceImpl extends ServiceImpl<TsTaskMapper, TsTask> impleme
jdbcTemplate.execute("SET UNIQUE_CHECKS=1;");
jdbcTemplate.execute("COMMIT;");
LOGGER.info("批量导入完成,共执行 {} 条SQL", total);
LOGGER.info("批量导入完成,共执行 {} 条SQL其中失败 {} 条", total, failedCount);
// 如果有失败的SQL返回false
if (failedCount > 0) {
LOGGER.warn("SQL导入过程中有 {} 条SQL执行失败", failedCount);
return false;
}
return true;
} catch (Exception e) {
LOGGER.error("SQL导入失败已执行 {} 条SQL", total, e);
@ -1281,7 +1291,8 @@ public class TsTaskServiceImpl extends ServiceImpl<TsTaskMapper, TsTask> impleme
}
private void batchExecute(List<String> sqlList) {
private int batchExecute(List<String> sqlList) {
int failedCount = 0;
try {
jdbcTemplate.batchUpdate(sqlList.toArray(new String[0]));
} catch (DataAccessException e) {
@ -1291,9 +1302,11 @@ public class TsTaskServiceImpl extends ServiceImpl<TsTaskMapper, TsTask> impleme
jdbcTemplate.update(sql);
} catch (Exception ex) {
LOGGER.warn("跳过失败SQL{}", ex.getMessage());
failedCount++;
}
}
}
return failedCount;
}
// =========================== INSERT 合并优化 ===========================

View File

@ -0,0 +1,31 @@
package com.yfd.platform.utils;
//双指针线性插值
public class InterpCursor {
private final double[] t;
private final double[] v;
private int idx = 0;
public InterpCursor(double[] t, double[] v) {
this.t = t;
this.v = v;
}
public double valueAt(double x) {
int n = t.length;
if (n == 0) return 0;
if (x <= t[0] || x >= t[n - 1]) return 0;
while (idx < n - 2 && t[idx + 1] < x) {
idx++;
}
double t0 = t[idx];
double t1 = t[idx + 1];
if (x < t0 || x > t1) return 0;
double r = (x - t0) / (t1 - t0);
return v[idx] + r * (v[idx + 1] - v[idx]);
}
}

View File

@ -0,0 +1,2 @@
BaseImu FCC GpsSts UtcInsIn WxInsIn WyInsIn WzInsIn AxInsIn AyInsIn AzInsIn LatBaseIn LonBaseIn HgtBaseIn LatVisIn LonVisIn HgtVisIn ppsofvis recievedata Reset LatJG LonJG HgtJG VnJG VuJG VeJG RollJG YawJG PitchJG SysSts LatInsOut LonInsOut HgtInsOut LatBaseOut LonBaseOut HgtBaseOut VnInsOut VuInsOut VeInsOut RollInsOut YawInsOut PitchInsOut FeedBackCnt uiUTCSecond lati hgt longi vx vy vz roll yaw pitch HGps gyrox gyroy gyroz accx accy accz Sts RecPps RecVisData NaviStatus PosVisx PosVisy PosVisz SouthLati SouthLogi SouthH NorthLati NorthLogi NorthH MatchStatus VxMatch VyMatch VzMatch posxMatch posyMatch poszMatch attixMatch attiyMatch attizMatch utc attix attiy attiz posx posy posz vnx vny vnz status InsVisSts Direction FeedbackCnt_frontview ImgCnt InsNavMode bak1 bak2