package com.ruoyi.system.controller; import com.alibaba.fastjson.JSONObject; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.utils.file.FileUploadUtils; import com.ruoyi.common.utils.image.ImageCompressUtil; import com.ruoyi.system.utils.AliOCRUtil; import com.ruoyi.system.utils.BaiduOCRUtil; import com.ruoyi.system.utils.TencentOCRUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * OCR识别Controller * 支持阿里云OCR和百度OCR服务 * @author ruoyi */ @RestController @RequestMapping("/system/ocr") public class OCRController extends BaseController { @Autowired private AliOCRUtil aliOCRUtil; // 支持的OCR识别类型 private static final List SUPPORTED_TYPES = Arrays.asList("General", "Invoice", "IdCard", "HandWriting"); /** * 上传图片并进行OCR识别 * @param file 上传的图片文件 * @param type 识别类型(General-通用, Invoice-发票, IdCard-身份证, HandWriting-手写体) * @param provider OCR服务提供商(ali-阿里云, baidu-百度) * @return OCR识别结果 */ @PostMapping(value = "/recognize", consumes = "multipart/form-data") public AjaxResult recognizeImage(@RequestParam("file") MultipartFile file, @RequestParam(value = "type", defaultValue = "General") String type, @RequestParam(value = "provider", defaultValue = "ali") String provider, @RequestParam(value = "itemNames", required = false) String[] itemNames) { try { if (file.isEmpty()) { return error("上传图片不能为空"); } // 验证识别类型 if (!SUPPORTED_TYPES.contains(type)) { return error("不支持的识别类型: " + type + ", 支持的类型: " + String.join(",", SUPPORTED_TYPES)); } // 智能压缩图片(自动处理超过3MB的图片) File tempFile = ImageCompressUtil.compressForOCR(file); // 根据提供商调用不同的OCR服务 JSONObject ocrResult; if ("baidu".equalsIgnoreCase(provider)) { // 百度OCR只支持部分类型 if ("General".equals(type)) { ocrResult = BaiduOCRUtil.generalRecognize(tempFile); } else if ("HandWriting".equals(type)) { ocrResult = BaiduOCRUtil.handwritingRecognize(tempFile); } else { ocrResult = BaiduOCRUtil.generalRecognize(tempFile); // 默认使用通用识别 } } else if ("tencent".equalsIgnoreCase(provider)) { // 腾讯云OCR只支持部分类型 if ("General".equals(type)) { ocrResult = TencentOCRUtil.generalRecognize(tempFile); } else if ("HandWriting".equals(type)) { ocrResult = TencentOCRUtil.handwritingRecognize(tempFile.getAbsolutePath(), itemNames); } else { ocrResult = TencentOCRUtil.generalRecognize(tempFile); // 默认使用通用识别 } } else { // 阿里云OCR ocrResult = AliOCRUtil.recognizeTextByFile(tempFile, type); } // 删除临时文件 tempFile.delete(); // 构建返回结果 Map result = new HashMap<>(); result.put("ocrResult", ocrResult); result.put("fileName", file.getOriginalFilename()); result.put("originalSize", file.getSize()); result.put("processedSize", tempFile.length()); result.put("compressed", file.getSize() > tempFile.length()); result.put("type", type); result.put("provider", provider); if (ocrResult.getBooleanValue("success")) { return success(result); } else { return error("OCR识别失败: " + ocrResult.getString("error")); } } catch (Exception e) { logger.error("OCR识别异常", e); return error("OCR识别异常: " + e.getMessage()); } } /** * 通过图片URL进行OCR识别 * @param imageUrl 图片URL地址 * @param type 识别类型(General-通用, Invoice-发票, IdCard-身份证, HandWriting-手写体) * @param provider OCR服务提供商(ali-阿里云, baidu-百度) * @return OCR识别结果 */ @GetMapping("/recognizeByUrl") public AjaxResult recognizeByUrl(@RequestParam("imageUrl") String imageUrl, @RequestParam(value = "type", defaultValue = "General") String type, @RequestParam(value = "provider", defaultValue = "ali") String provider, @RequestParam(value = "itemNames", required = false) String[] itemNames) { try { // 验证识别类型 if (!SUPPORTED_TYPES.contains(type)) { return error("不支持的识别类型: " + type + ", 支持的类型: " + String.join(",", SUPPORTED_TYPES)); } // 根据提供商调用不同的OCR服务 JSONObject ocrResult; if ("baidu".equalsIgnoreCase(provider)) { // 百度OCR只支持部分类型 if ("General".equals(type)) { ocrResult = BaiduOCRUtil.generalRecognize(imageUrl); } else if ("HandWriting".equals(type)) { ocrResult = BaiduOCRUtil.handwritingRecognize(imageUrl); } else { ocrResult = BaiduOCRUtil.generalRecognize(imageUrl); // 默认使用通用识别 } } else if ("tencent".equalsIgnoreCase(provider)) { // 腾讯云OCR只支持部分类型 if ("General".equals(type)) { ocrResult = TencentOCRUtil.generalRecognize(imageUrl); } else if ("HandWriting".equals(type)) { ocrResult = TencentOCRUtil.handwritingRecognize(imageUrl, itemNames); } else { ocrResult = TencentOCRUtil.generalRecognize(imageUrl); // 默认使用通用识别 } } else { // 阿里云OCR ocrResult = AliOCRUtil.recognizeTextByUrl(imageUrl, type); } // 构建返回结果 Map result = new HashMap<>(); result.put("ocrResult", ocrResult); result.put("imageUrl", imageUrl); result.put("type", type); result.put("provider", provider); if (ocrResult.getBooleanValue("success")) { return success(result); } else { return error("OCR识别失败: " + ocrResult.getString("error")); } } catch (Exception e) { logger.error("OCR识别异常", e); return error("OCR识别异常: " + e.getMessage()); } } /** * 获取支持的OCR识别类型列表 * @return 识别类型列表 */ @GetMapping("/types") public AjaxResult getSupportedTypes() { Map result = new HashMap<>(); result.put("types", SUPPORTED_TYPES); List> typeList = SUPPORTED_TYPES.stream().map(type -> { Map typeInfo = new HashMap<>(); typeInfo.put("value", type); // 根据类型设置显示名称 switch (type) { case "General": typeInfo.put("label", "通用文字识别"); break; case "Invoice": typeInfo.put("label", "发票识别"); break; case "IdCard": typeInfo.put("label", "身份证识别"); break; case "HandWriting": typeInfo.put("label", "手写体识别"); break; default: typeInfo.put("label", type); break; } return typeInfo; }).collect(Collectors.toList()); result.put("typeList", typeList); return success(result); } /** * 获取支持的OCR服务提供商列表 * @return OCR服务提供商列表 */ @GetMapping("/providers") public AjaxResult getSupportedProviders() { Map result = new HashMap<>(); List> providerList = Arrays.asList( createProviderInfo("ali", "阿里云OCR", true), createProviderInfo("baidu", "百度OCR", true), createProviderInfo("tencent", "腾讯云OCR", true) ); result.put("providers", providerList); return success(result); } /** * 提取OCR结果中的目标字段 * @param ocrResult OCR原始结果 * @return 提取的字段信息 */ @PostMapping("/extractFields") public AjaxResult extractFields(@RequestBody JSONObject ocrResult) { try { // 检查是否为百度OCR结果 String provider = ocrResult.getString("provider"); Map extracted; if ("baidu".equalsIgnoreCase(provider)) { extracted = BaiduOCRUtil.extractTargetFields(ocrResult); } else if ("tencent".equalsIgnoreCase(provider)) { extracted = TencentOCRUtil.extractTargetFields(ocrResult); } else { extracted = AliOCRUtil.extractTargetFields(ocrResult); } return success(extracted); } catch (Exception e) { logger.error("字段提取异常", e); return error("字段提取异常: " + e.getMessage()); } } /** * 腾讯云手写体识别(支持自定义字段提取) * @param file 上传的图片文件 * @param itemNames 需要提取的字段名称数组 * @return 识别结果 Map,key为字段名,value为识别内容 */ @PostMapping(value = "/tencent/handwriting", consumes = "multipart/form-data") public AjaxResult tencentHandwritingRecognize(@RequestParam("file") MultipartFile file, @RequestParam(value = "itemNames", required = false) String[] itemNames) { try { if (file.isEmpty()) { return error("上传图片不能为空"); } // 智能压缩图片(自动处理超过3MB的图片) File tempFile = ImageCompressUtil.compressForOCR(file); // 调用腾讯云手写体识别 Map resultMap = TencentOCRUtil.handwritingRecognizeWith(tempFile.getAbsolutePath(), itemNames); // 删除临时文件 tempFile.delete(); // 检查是否有错误 if (resultMap.containsKey("error")) { return error("腾讯云OCR手写体识别失败: " + resultMap.get("error")); } // 构建返回结果 Map result = new HashMap<>(); result.put("fileName", file.getOriginalFilename()); result.put("type", "HandWriting"); result.put("provider", "tencent"); result.put("fields", resultMap); result.put("fieldCount", resultMap.size()); return success(result); } catch (Exception e) { logger.error("腾讯云OCR手写体识别异常", e); return error("腾讯云OCR手写体识别异常: " + e.getMessage()); } } /** * 腾讯云手写体识别通过URL(支持自定义字段提取) * @param imageUrl 图片URL地址 * @param itemNames 需要提取的字段名称数组 * @return 识别结果 Map,key为字段名,value为识别内容 */ @GetMapping("/tencent/handwritingByUrl") public AjaxResult tencentHandwritingRecognizeByUrl(@RequestParam("imageUrl") String imageUrl, @RequestParam(value = "itemNames", required = false) String[] itemNames) { try { // 调用腾讯云手写体识别 Map resultMap = TencentOCRUtil.handwritingRecognizeWith(imageUrl, itemNames); // 检查是否有错误 if (resultMap.containsKey("error")) { return error("腾讯云OCR手写体识别失败: " + resultMap.get("error")); } // 构建返回结果 Map result = new HashMap<>(); result.put("imageUrl", imageUrl); result.put("type", "HandWriting"); result.put("provider", "tencent"); result.put("fields", resultMap); result.put("fieldCount", resultMap.size()); return success(result); } catch (Exception e) { logger.error("腾讯云OCR手写体识别异常", e); return error("腾讯云OCR手写体识别异常: " + e.getMessage()); } } /** * 腾讯云手写体识别(支持多图片批量识别) * @param files 上传的图片文件数组 * @param itemNames 需要提取的字段名称数组 * @return 识别结果,合并所有图片的识别字段 */ @PostMapping(value = "/tencent/handwriting/batch", consumes = "multipart/form-data") public AjaxResult tencentHandwritingRecognizeBatch(@RequestParam("files") MultipartFile[] files, @RequestParam(value = "itemNames", required = false) String[] itemNames) { try { if (files == null || files.length == 0) { return error("上传图片不能为空"); } // 合并所有图片的识别结果 Map mergedResultMap = new HashMap<>(); int successCount = 0; int failCount = 0; StringBuilder errorMessages = new StringBuilder(); for (MultipartFile file : files) { if (file.isEmpty()) { continue; } try { // 智能压缩图片(自动处理超过3MB的图片) File tempFile = ImageCompressUtil.compressForOCR(file); // 调用腾讯云手写体识别 Map resultMap = TencentOCRUtil.handwritingRecognizeWith(tempFile.getAbsolutePath(), itemNames); // 删除临时文件 tempFile.delete(); // 检查是否有错误 if (resultMap.containsKey("error")) { failCount++; errorMessages.append(file.getOriginalFilename()).append(":").append(resultMap.get("error")).append("; "); logger.warn("图片 {} 识别失败: {}", file.getOriginalFilename(), resultMap.get("error")); } else { // 合并识别结果(如果key已存在,不覆盖) for (Map.Entry entry : resultMap.entrySet()) { if (!mergedResultMap.containsKey(entry.getKey()) || mergedResultMap.get(entry.getKey()).isEmpty()) { mergedResultMap.put(entry.getKey(), entry.getValue()); } } successCount++; logger.info("图片 {} 识别成功,提取 {} 个字段", file.getOriginalFilename(), resultMap.size()); } } catch (Exception e) { failCount++; errorMessages.append(file.getOriginalFilename()).append(":").append(e.getMessage()).append("; "); logger.error("处理图片 {} 时发生异常", file.getOriginalFilename(), e); } } // 构建返回结果 Map result = new HashMap<>(); result.put("type", "HandWriting"); result.put("provider", "tencent"); result.put("fields", mergedResultMap); result.put("fieldCount", mergedResultMap.size()); result.put("totalImages", files.length); result.put("successCount", successCount); result.put("failCount", failCount); if (failCount > 0) { result.put("errors", errorMessages.toString()); } if (successCount == 0) { return error("所有图片识别失败: " + errorMessages.toString()); } return success(result); } catch (Exception e) { logger.error("腾讯云OCR手写体批量识别异常", e); return error("腾讯云OCR手写体批量识别异常: " + e.getMessage()); } } /** * 创建服务提供商信息 * @param value 服务提供商标识 * @param label 服务提供商显示名称 * @param available 是否可用 * @return 服务提供商信息 */ private Map createProviderInfo(String value, String label, boolean available) { Map providerInfo = new HashMap<>(); providerInfo.put("value", value); providerInfo.put("label", label); providerInfo.put("available", String.valueOf(available)); return providerInfo; } }