
在使用 Apache Camel Bindy 解析 CSV 文件时,遇到格式错误(如非数字字段)会导致 NumberFormatException 并中断整个路由;本文介绍两种健壮方案——正则预过滤与自定义容错 DataFormat,实现自动跳过非法行并继续处理有效记录。
在使用 apache camel bindy 解析 csv 文件时,遇到格式错误(如非数字字段)会导致 `numberformatexception` 并中断整个路由;本文介绍两种健壮方案——正则预过滤与自定义容错 dataformat,实现自动跳过非法行并继续处理有效记录。
Apache Camel 的 BindyDataFormat 默认不具备容错能力:一旦某一行数据无法映射(例如将字符串 "Test" 赋值给 double sellRate 字段),便会抛出 NumberFormatException,导致当前 Exchange 失败,且后续记录也无法处理。为保障批量解析的稳定性,推荐以下两种生产级解决方案:
✅ 方案一:前置正则过滤(推荐 · 简单高效)
利用 .split() + .filter() 在反序列化前对原始 CSV 行进行语法校验,仅让符合格式的行进入 Bindy 流程:
from("file:" + baseInputPath + "/exchangerates" + "?noop=true")
.idempotentConsumer(header(CAMEL_FILE_NAME),
FileIdempotentRepository.fileIdempotentRepository(
new File(baseInputPath + "/tracker/exchangeratefiles.txt")))
.log("Processing exchange rate file: ${header.CamelFileName}")
// 按行切分,并过滤掉不匹配格式的记录
.split(body().tokenize("
"))
.streaming() // 启用流式处理,避免内存积压
.filter(simple("${body} regex '^[A-Z]{3}\*[^*]+\*[0-9.]+\*[0-9.]+\*[0-9.]+$'"))
.unmarshal().bindy(BindyType.Csv, ExchangeRateBindy.class)
.process(exchange -> {
ExchangeRateBindy bindy = exchange.getIn().getBody(ExchangeRateBindy.class);
if (bindy != null) {
exchangeRateService.save(bindy.toExchangeRate());
}
})
.end(); // 结束 filter 分支? 正则说明:
^[A-Z]{3}\*[^*]+\*[0-9.]+\*[0-9.]+\*[0-9.]+$
^[A-Z]{3}:货币代码(3位大写字母)\*:字面量星号分隔符[^*]+:货币名称(不含*的任意字符)[0-9.]+:三次浮点数格式(支持1.0332或812.7459194)$:确保整行匹配,防止部分匹配干扰
✅ 优势:零侵入 Bindy,逻辑清晰,性能高,天然跳过非法行,无需异常捕获。
使用 qbo-mileage CLI 及用户凭证,从 Airtable、Outlook 或 Google Calendar 记录生成 QuickBooks Online 里程 CSV 文件。
✅ 方案二:封装容错型 Bindy DataFormat(高级 · 灵活可控)
若需更精细控制(如记录错误行、动态 fallback 值),可封装一个 FaultTolerantBindyCsvDataFormat:
public class FaultTolerantBindyCsvDataFormat extends BindyCsvDataFormat {
public FaultTolerantBindyCsvDataFormat(Class<?> type) {
super(type);
}
@Override
public Object unmarshal(Exchange exchange, InputStream stream) throws Exception {
List<String> lines = IOUtils.readLines(stream, "UTF-8");
List<Object> validObjects = new ArrayList<>();
for (String line : lines) {
if (line.trim().isEmpty()) continue;
try {
// 使用父类逻辑逐行解析
Object obj = super.unmarshal(exchange, new ByteArrayInputStream(line.getBytes(StandardCharsets.UTF_8)));
validObjects.add(obj);
} catch (Exception e) {
log.warn("Skipped invalid CSV line: '{}', cause: {}", line, e.getMessage());
// 跳过该行,不中断流程
}
}
return validObjects;
}
}在路由中使用:
.unmarshal(new FaultTolerantBindyCsvDataFormat(ExchangeRateBindy.class))
.process(exchange -> {
List<ExchangeRateBindy> list = exchange.getIn().getBody(List.class);
if (list != null) {
list.forEach(bindy -> exchangeRateService.save(bindy.toExchangeRate()));
}
});⚠️ 注意事项:
- 使用
.split().streaming()可显著降低内存占用,尤其适用于大文件; - 正则过滤需严格匹配实际 CSV 格式(注意转义
*为\*); - 若 CSV 含引号包裹字段或换行符,正则方案需升级为完整 CSV 解析器(如 OpenCSV)预检;
-
skipFirstLine = false时,请确保首行无标题;若含标题,建议设为true或在正则中排除首行。
通过上述任一方式,您即可实现「失败隔离」:单条记录错误不再阻塞全局处理,系统鲁棒性与可观测性同步提升。

















