package com.ruoyi.business.ocr; import cn.hutool.core.io.FileUtil; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import lombok.extern.slf4j.Slf4j; import java.io.File; /** * ry-ocr Java 调用客户端 * * 依赖:hutool-http, hutool-json, hutool-core, lombok * * 用法: * OcrClient client = new OcrClient("http://127.0.0.1:8801"); * InvoiceResult r = client.recognize(new File("d:/发票.pdf")); * InvoiceResult r2 = client.recognizeByUrl("https://oss.example.com/xxx.png"); * System.out.println(r.getFields().getAmount()); */ @Slf4j public class OcrClient { private final String baseUrl; public OcrClient(String baseUrl) { this.baseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl; } /** 健康检查 */ public boolean ping() { try (HttpResponse resp = HttpRequest.get(baseUrl + "/health").timeout(3000).execute()) { return resp.getStatus() == 200 && "ok".equals(JSONUtil.parseObj(resp.body()).getStr("status")); } catch (Exception e) { log.warn("ocr ping failed: {}", e.getMessage()); return false; } } /** 识别发票(图片或 PDF) */ public InvoiceResult recognize(File file) { try (HttpResponse resp = HttpRequest.post(baseUrl + "/recognize/invoice") .form("file", file) .timeout(60_000) .execute()) { String body = resp.body(); JSONObject json = JSONUtil.parseObj(body); if (resp.getStatus() != 200) { throw new RuntimeException("OCR 调用失败: " + resp.getStatus() + " " + body); } return parse(json); } } /** * 从 URL 识别发票: 后端下载 OSS URL 到临时文件 → recognize → 清理临时文件. * 临时文件目录: System.getProperty("java.io.tmpdir")/ry-ocr/ * * @param url OSS 可访问 URL * @return 识别结果 */ public InvoiceResult recognizeByUrl(String url) { if (url == null || url.isEmpty()) { throw new IllegalArgumentException("ossUrl 不能为空"); } File tmpDir = new File(System.getProperty("java.io.tmpdir"), "ry-ocr"); if (!tmpDir.exists() && !tmpDir.mkdirs()) { throw new RuntimeException("无法创建临时目录: " + tmpDir.getAbsolutePath()); } // 从 URL 截取文件名, 保留后缀 (用于 ry-ocr 推断图片/PDF) String name = url.substring(url.lastIndexOf('/') + 1); if (name.indexOf('?') >= 0) name = name.substring(0, name.indexOf('?')); if (name.indexOf('.') < 0) name = name + ".png"; File tmp = new File(tmpDir, System.currentTimeMillis() + "_" + name); try { long size = HttpUtil.downloadFile(url, tmp); if (size <= 0) { throw new RuntimeException("OSS 文件下载失败或为空: " + url); } log.info("OCR 下载: url={} size={}B tmp={}", url, size, tmp.getAbsolutePath()); return recognize(tmp); } finally { FileUtil.del(tmp); } } private InvoiceResult parse(JSONObject json) { InvoiceResult r = new InvoiceResult(); r.setSuccess(json.getBool("success", false)); r.setRawText(json.getStr("rawText", "")); r.setEngine(json.getStr("engine", "")); r.setPageCount(json.getInt("pageCount", 1)); r.setElapsedMs(json.getInt("elapsedMs", 0)); r.setError(json.getStr("error")); JSONObject f = json.getJSONObject("fields"); if (f != null) { InvoiceFields fields = new InvoiceFields(); fields.setInvoiceType(f.getStr("invoiceType")); fields.setInvoiceNo(f.getStr("invoiceNo")); fields.setInvoiceCode(f.getStr("invoiceCode")); fields.setInvoiceDate(f.getStr("invoiceDate")); fields.setAmount(f.getDouble("amount")); fields.setAmountCn(f.getStr("amountCn")); fields.setAmountPretax(f.getDouble("amount_pretax")); fields.setTaxAmount(f.getDouble("taxAmount")); fields.setSellerName(f.getStr("sellerName")); fields.setSellerTaxNo(f.getStr("sellerTaxNo")); fields.setBuyerName(f.getStr("buyerName")); fields.setBuyerTaxNo(f.getStr("buyerTaxNo")); fields.setAmountMatch(f.getBool("amountMatch")); r.setFields(fields); } return r; } }