Java 17 视觉识别结果的金额与字段门禁实现

14 阅读1分钟

把票据图片交给视觉模型后,最容易犯的错误是把识别出的金额直接报销。图像理解擅长找候选字段,却可能受模糊、反光和版式影响。安全做法是让模型只“提取”,让 Java 用 BigDecimal、币种白名单和人工阈值决定能否进入下一步。你将得到一个无需密钥也能读懂和编译的最小门禁。

官方模型文档把图像能力与模型可用性分开呈现,实际模型 ID、文件大小和账户权限需在调用前核验。Java 没有必要依赖不确定的 SDK:如服务只提供 REST,用 Java 17 的 HttpClient 即可,并给请求设超时。

flowchart LR
P[票据图片] --> X[视觉模型提取候选 JSON]
X --> F[Java 字段解析]
F --> G[币种/金额/商户门禁]
G -->|合格| R[人工报销复核]
G -->|不合格| B[退回补拍或人工录入]

环境与代码

安装 Java 17+,保存为 ReceiptGate.java,运行 javac ReceiptGate.java && java ReceiptGate。没有外部依赖;真实 API 请求可使用 HttpClient,将 OPENAI_API_KEY 通过 System.getenv 读取,绝不硬编码。以下候选 JSON 用教学数据模拟,因此不会上传票据。

import java.math.BigDecimal;
import java.util.Map;

public class ReceiptGate {
  static final BigDecimal LIMIT = new BigDecimal("1000.00");
  static final Map<String, String> ALLOWED = Map.of("CNY", "人民币", "USD", "美元");

  static Result review(String merchant, String currency, String amount) {
    if (merchant == null || merchant.isBlank()) return new Result(false, "缺少商户");
    if (!ALLOWED.containsKey(currency)) return new Result(false, "不支持的币种");
    try {
      BigDecimal value = new BigDecimal(amount);
      if (value.signum() <= 0) return new Result(false, "金额必须大于零");
      if (value.compareTo(LIMIT) > 0) return new Result(false, "超过自动队列上限");
      return new Result(true, "进入人工复核:" + ALLOWED.get(currency));
    } catch (NumberFormatException e) {
      return new Result(false, "模型金额格式无效");
    }
  }
  record Result(boolean ok, String message) {}
  public static void main(String[] args) {
    Result r = review("示例超市", "CNY", "128.50");
    System.out.println("ok=" + r.ok() + ", " + r.message());
    // 真实 HTTP 调用应使用 HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
    // 并为 request.timeout(Duration.ofSeconds(20));响应仍必须经过 review。
  }
}

预期输出为 ok=true, 进入人工复核:人民币。为何使用 BigDecimal 而不是 double?货币比较需要避免二进制浮点误差。常见失败包括:图片不清导致小数点错位——限制自动阈值并保留原图供复核;模型返回“¥128.5”——要求结构化数字字段,解析失败时拒绝;请求超时或限流——连接超时 10 秒、请求超时 20 秒,重试前检查请求是否已入队;同一票据重复提交——生产中以图片哈希和发票号做幂等键。

它适合低风险票据预分流,不适合自动付款、税务认定或伪造判断。工程化还应增加版式置信度、原图授权与保存期限、审计记录及人工纠错回流。5 分钟实践:把上限改为从环境变量读取,并为“1000.01”写一个拒绝测试。

除了金额,你会如何证明一张票据确实属于本次报销?

关注「蜗牛聊AI」,一起看懂技术变化背后的真正机会。


本文首发于 java4u.cn,转载请注明出处。