diff --git a/klp-admin/src/main/resources/application.yml b/klp-admin/src/main/resources/application.yml index 2cc7ef46..36ac95b8 100644 --- a/klp-admin/src/main/resources/application.yml +++ b/klp-admin/src/main/resources/application.yml @@ -296,57 +296,8 @@ flowable: check-process-definitions: false # 关闭历史任务定时任务job async-history-executor-activate: false -# 多模态分析功能配置示例 -# 将此配置添加到你的 application.yml 或 application-dev.yml 中 -# 多模态API配置 -multimodal: - # API地址 - api-url: https://api.siliconflow.cn/v1/chat/completions - # 模型名称 - model-name: Qwen/Qwen2.5-VL-72B-Instruct - # API密钥 - token: sk-sbmuklhrcxqlsucufqebiibauflxqfdafqjxaedtwirurtrc - -# 日志配置 -logging: - level: - com.klp.service.MultimodalService: DEBUG - com.klp.controller.MultimodalController: DEBUG - com.klp.utils.ImageUtils: DEBUG - -# 服务器配置 -server: - port: 8080 - servlet: - context-path: / - -# Spring配置 -spring: - application: - name: klp-wms-multimodal - - # 文件上传配置 - servlet: - multipart: - max-file-size: 10MB - max-request-size: 10MB - - # Jackson配置 - jackson: - date-format: yyyy-MM-dd HH:mm:ss - time-zone: GMT+8 - default-property-inclusion: non_null - -# 线程池配置 -thread: - pool: - core-size: 5 - max-size: 10 - queue-capacity: 100 - keep-alive-seconds: 60 - -# 超时配置 -timeout: - connect: 30000 # 连接超时 30秒 - read: 60000 # 读取超时 60秒 +# 图像识别 OCR 配置(语言包绝对路径) +tesseract: + data-path: D:/tessdata # Windows开发环境 + # datapath: /opt/tessdata # Linux生产环境 diff --git a/klp-common/src/main/java/com/klp/common/config/TesseractConfig.java b/klp-common/src/main/java/com/klp/common/config/TesseractConfig.java new file mode 100644 index 00000000..82dbaa31 --- /dev/null +++ b/klp-common/src/main/java/com/klp/common/config/TesseractConfig.java @@ -0,0 +1,14 @@ +package com.klp.common.config; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Data +@Component +public class TesseractConfig { + + @Value("${tesseract.data-path}") + private String datapath; + +} diff --git a/klp-wms/src/main/java/com/klp/controller/Application.java b/klp-wms/src/main/java/com/klp/controller/Application.java deleted file mode 100644 index be90f5d5..00000000 --- a/klp-wms/src/main/java/com/klp/controller/Application.java +++ /dev/null @@ -1,200 +0,0 @@ -package com.klp.controller; - -import net.sourceforge.tess4j.ITesseract; -import net.sourceforge.tess4j.Tesseract; -import net.sourceforge.tess4j.TesseractException; - -import javax.imageio.ImageIO; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; - -public class Application { - - /** - * @Description: 识别图片中的文字 - * @param args - */ - public static void main(String[] args) { - try { - // 获取本地图片 - // File file = new File("D:\\test.png"); - - String imageUrl = "http://47.117.71.33:11296/api/v1/buckets/fadapp-update/objects/download?preview=true&prefix=%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20250731172707.png&version_id=null"; - // 读取网络图片为 BufferedImage - URL url = new URL(imageUrl); - InputStream inputStream = url.openStream(); - BufferedImage image = ImageIO.read(inputStream); - inputStream.close(); - // 预处理图片 - BufferedImage bufferedImage = preprocessImage(image); - - System.out.println("开始OCR识别..."); - - // 创建Tesseract对象 - ITesseract tesseract = new Tesseract(); - - // 设置字体库路径(绝对路径) - tesseract.setDatapath("D:\\tessdata"); - -/* File tessdataDir = exportTessdataToTemp(); // 从 classpath 复制到临时目录 - tesseract.setDatapath(tessdataDir.getAbsolutePath());*/ - - // 设置语言简体中文 - tesseract.setLanguage("chi_sim"); - - // 优化OCR配置 - tesseract.setPageSegMode(6); // 假设统一的文本块 - tesseract.setOcrEngineMode(3); // 使用默认引擎 - - // 执行OCR识别图片 - String result = tesseract.doOCR(bufferedImage); - - System.out.println("\n=== 原始识别结果 ==="); - System.out.println(result); - - // 清理和格式化结果 - String cleanedResult = cleanOcrResult(result); - - System.out.println("\n=== 清理后的结果 ==="); - System.out.println(cleanedResult); - - // 分行显示结果 - System.out.println("\n=== 分行显示结果 ==="); - String[] lines = cleanedResult.split("-"); - for (int i = 0; i < lines.length; i++) { - if (!lines[i].trim().isEmpty()) { - System.out.println((i + 1) + ". " + lines[i].trim()); - } - } - - } catch (TesseractException e) { - System.err.println("Tesseract OCR错误: " + e.getMessage()); - e.printStackTrace(); - } catch (Exception e) { - System.err.println("其他错误: " + e.getMessage()); - e.printStackTrace(); - } - } - - /** - * 清理OCR识别结果 - * @param result 原始识别结果 - * @return 清理后的结果 - */ - private static String cleanOcrResult(String result) { - if (result == null || result.trim().isEmpty()) { - return ""; - } - - // 替换常见的OCR错误 - String cleaned = result - // 替换回车和换行 - .replaceAll("\\r|\\n", "-") - // 移除多余的空格 - .replaceAll("\\s+", " ") - // 修复常见的OCR错误 - .replaceAll("英声租", "产品名称") - .replaceAll("库咤埕号", "产品型号") - .replaceAll("产晓序列号", "产品序列号") - .replaceAll("购买纳证缉号", "购买凭证编号") - .replaceAll("质保条孰", "质保条款") - .replaceAll("本亢命", "本产品") - .replaceAll("质僚服务", "质保服务") - .replaceAll("质保朝内", "质保期内") - .replaceAll("团素", "因素") - .replaceAll("质量闰题", "质量问题") - .replaceAll("免贵维修", "免费维修") - .replaceAll("更挺服", "更换服") - .replaceAll("不包挂", "不包括") - .replaceAll("溢用", "滥用") - .replaceAll("探环", "损坏") - .replaceAll("取葛保管", "妥善保管") - .replaceAll("雷凭吊证明", "需凭此证明") - .replaceAll("客户信恩", "客户信息") - .replaceAll("姆钗", "姓名") - .replaceAll("联系电语", "联系电话") - .replaceAll("电子邹件", "电子邮件") - .replaceAll("地抛", "地址") - .replaceAll("摒权代表", "授权代表") - .replaceAll("介为示例", "仅为示例") - .replaceAll("根揪实际情况", "根据实际情况") - .replaceAll("调教", "调整") - // 移除多余的空格 - .trim(); - - return cleaned; - } - - /** - * 图片预处理(可选) - * @param inputFile 输入图片 - * @param outputFile 输出图片 - */ - private static void preprocessImage(File inputFile, File outputFile) { - try { - BufferedImage image = ImageIO.read(inputFile); - - // 转换为灰度图 - BufferedImage grayImage = new BufferedImage( - image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY); - - Graphics2D g2d = grayImage.createGraphics(); - g2d.drawImage(image, 0, 0, null); - g2d.dispose(); - - // 保存预处理后的图片 - ImageIO.write(grayImage, "png", outputFile); - - System.out.println("图片预处理完成: " + outputFile.getAbsolutePath()); - - } catch (IOException e) { - System.err.println("图片预处理失败: " + e.getMessage()); - } - } - - /** - * 灰度化图像(直接处理 BufferedImage) - * @param image 原始图片 - * @return 灰度图 - */ - private static BufferedImage preprocessImage(BufferedImage image) { - BufferedImage grayImage = new BufferedImage( - image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY); - - Graphics2D g2d = grayImage.createGraphics(); - g2d.drawImage(image, 0, 0, null); - g2d.dispose(); - - return grayImage; - } - - public static File exportTessdataToTemp() throws IOException { - String[] languageFiles = {"chi_sim.traineddata","chi_sim_vert.traineddata"}; - // 明确指定临时目录路径(避免路径分隔符问题) - File tempTessdataDir = new File(System.getProperty("java.io.tmpdir"), "tessdata"); - if (!tempTessdataDir.exists()) { - tempTessdataDir.mkdirs(); - } - - ClassLoader classLoader = Application.class.getClassLoader(); - for (String filename : languageFiles) { - // 使用正确的资源路径(注意开头的/) - try (InputStream in = classLoader.getResourceAsStream("\\tessdata\\" + filename)) { - if (in == null) { - throw new FileNotFoundException("语言包未找到: /tessdata/" + filename); - } - File outFile = new File(tempTessdataDir, filename); - Files.copy(in, outFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - } - } - return tempTessdataDir; // 返回的是 tessdata 目录本身 - } - -} diff --git a/klp-wms/src/main/java/com/klp/controller/WmsPurchasePlanController.java b/klp-wms/src/main/java/com/klp/controller/WmsPurchasePlanController.java index 15b9ebe5..e51706d7 100644 --- a/klp-wms/src/main/java/com/klp/controller/WmsPurchasePlanController.java +++ b/klp-wms/src/main/java/com/klp/controller/WmsPurchasePlanController.java @@ -2,7 +2,10 @@ package com.klp.controller; import java.util.List; import java.util.Arrays; +import java.util.Map; +import com.klp.domain.vo.RecognizeTextVo; +import com.klp.service.ITesseractOcrService; import lombok.RequiredArgsConstructor; import javax.servlet.http.HttpServletResponse; import javax.validation.constraints.*; @@ -35,6 +38,7 @@ import com.klp.common.core.page.TableDataInfo; public class WmsPurchasePlanController extends BaseController { private final IWmsPurchasePlanService iWmsPurchasePlanService; + private final ITesseractOcrService iTesseractOcrService; /** * 新增采购计划(含明细) @@ -52,6 +56,18 @@ public class WmsPurchasePlanController extends BaseController { return R.ok(planVo); } + /** + * 识别图片中的文字 + * @param request 图片地址 + * @return 识别出的文字 + */ + @PostMapping("/recognizeText") + public R recognize(@RequestBody Map request) { + String imgUrl = request.get("imgUrl"); + String text = iTesseractOcrService.recognizeText(imgUrl); + return R.ok(new RecognizeTextVo(text)); + } + /** * 查询采购计划主列表 */ diff --git a/klp-wms/src/main/java/com/klp/domain/FileInfo.java b/klp-wms/src/main/java/com/klp/domain/FileInfo.java index 7a9cdda2..e1f23e2f 100644 --- a/klp-wms/src/main/java/com/klp/domain/FileInfo.java +++ b/klp-wms/src/main/java/com/klp/domain/FileInfo.java @@ -1,6 +1,5 @@ package com.klp.domain; -import jdk.jfr.DataAmount; import lombok.Data; /** diff --git a/klp-wms/src/main/java/com/klp/domain/WmsPurchasePlanDetail.java b/klp-wms/src/main/java/com/klp/domain/WmsPurchasePlanDetail.java index 900cbadf..79901db6 100644 --- a/klp-wms/src/main/java/com/klp/domain/WmsPurchasePlanDetail.java +++ b/klp-wms/src/main/java/com/klp/domain/WmsPurchasePlanDetail.java @@ -45,6 +45,10 @@ public class WmsPurchasePlanDetail extends BaseEntity { * 单位 */ private String unit; + /** + * 附件 + */ + private String annex; /** * 状态( 0=新建,1=在途,2=到货,3=待审核,4=采购完成) */ diff --git a/klp-wms/src/main/java/com/klp/domain/bo/WmsPurchasePlanDetailBo.java b/klp-wms/src/main/java/com/klp/domain/bo/WmsPurchasePlanDetailBo.java index 38156c54..e7c68b3d 100644 --- a/klp-wms/src/main/java/com/klp/domain/bo/WmsPurchasePlanDetailBo.java +++ b/klp-wms/src/main/java/com/klp/domain/bo/WmsPurchasePlanDetailBo.java @@ -50,6 +50,11 @@ public class WmsPurchasePlanDetailBo extends BaseEntity { */ private String unit; + /** + * 附件 + */ + private String annex; + /** * 状态( 0=新建,1=在途,2=到货,3=待审核,4=采购完成) */ diff --git a/klp-wms/src/main/java/com/klp/domain/vo/RecognizeTextVo.java b/klp-wms/src/main/java/com/klp/domain/vo/RecognizeTextVo.java new file mode 100644 index 00000000..6bd969fa --- /dev/null +++ b/klp-wms/src/main/java/com/klp/domain/vo/RecognizeTextVo.java @@ -0,0 +1,19 @@ +package com.klp.domain.vo; + +import lombok.Data; + +@Data +public class RecognizeTextVo { + + /** + * 识别后的文本 + */ + private String text; + + public RecognizeTextVo() { + } + + public RecognizeTextVo(String text) { + this.text = text; + } +} diff --git a/klp-wms/src/main/java/com/klp/domain/vo/WmsPurchasePlanDetailVo.java b/klp-wms/src/main/java/com/klp/domain/vo/WmsPurchasePlanDetailVo.java index 2db753c0..1546f0a4 100644 --- a/klp-wms/src/main/java/com/klp/domain/vo/WmsPurchasePlanDetailVo.java +++ b/klp-wms/src/main/java/com/klp/domain/vo/WmsPurchasePlanDetailVo.java @@ -56,6 +56,12 @@ public class WmsPurchasePlanDetailVo { @ExcelProperty(value = "单位") private String unit; + /** + * 附件 + */ + @ExcelProperty(value = "附件") + private String annex; + /** * 状态( 0=新建,1=在途,2=到货,3=待审核,4=采购完成) */ diff --git a/klp-wms/src/main/java/com/klp/domain/vo/WmsStockVo.java b/klp-wms/src/main/java/com/klp/domain/vo/WmsStockVo.java index fd5d4473..4a6f7abb 100644 --- a/klp-wms/src/main/java/com/klp/domain/vo/WmsStockVo.java +++ b/klp-wms/src/main/java/com/klp/domain/vo/WmsStockVo.java @@ -80,5 +80,10 @@ public class WmsStockVo { @ExcelProperty(value = "备注") private String remark; + /** + * 仓库/库区名称 + */ + @ExcelProperty(value = "仓库/库区名称") + private String warehouseName; } diff --git a/klp-wms/src/main/java/com/klp/service/ITesseractOcrService.java b/klp-wms/src/main/java/com/klp/service/ITesseractOcrService.java new file mode 100644 index 00000000..9b3ad1c8 --- /dev/null +++ b/klp-wms/src/main/java/com/klp/service/ITesseractOcrService.java @@ -0,0 +1,11 @@ +package com.klp.service; + +public interface ITesseractOcrService { + + /** + * 识别网络图片 + * @param imageUrl 图片URL + * @return 识别结果 + */ + String recognizeText(String imageUrl); +} diff --git a/klp-wms/src/main/java/com/klp/service/impl/TesseractOcrServiceImpl.java b/klp-wms/src/main/java/com/klp/service/impl/TesseractOcrServiceImpl.java new file mode 100644 index 00000000..aef63eec --- /dev/null +++ b/klp-wms/src/main/java/com/klp/service/impl/TesseractOcrServiceImpl.java @@ -0,0 +1,124 @@ +package com.klp.service.impl; + +import com.klp.common.config.TesseractConfig; +import com.klp.service.ITesseractOcrService; +import net.sourceforge.tess4j.ITesseract; +import net.sourceforge.tess4j.Tesseract; +import net.sourceforge.tess4j.TesseractException; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +@Service +public class TesseractOcrServiceImpl implements ITesseractOcrService { + + @Resource + private TesseractConfig tesseractConfig; + @Override + public String recognizeText(String imageUrl){ + // 读取网络图片为 BufferedImage + BufferedImage image = null; + try { + URL url = new URL(imageUrl); + InputStream inputStream = url.openStream(); + image = ImageIO.read(inputStream); + inputStream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + // 预处理图片 + BufferedImage bufferedImage = preprocessImage(image); + System.out.println("开始OCR识别..."); + // 创建Tesseract对象 + ITesseract tesseract = new Tesseract(); + // 设置字体库路径(绝对路径) + tesseract.setDatapath(tesseractConfig.getDatapath()); + // 设置语言简体中文 + tesseract.setLanguage("chi_sim"); + // 优化OCR配置 + tesseract.setPageSegMode(6); // 假设统一的文本块 + tesseract.setOcrEngineMode(3); // 使用默认引擎 + // 执行OCR识别图片 + String result = null; + try { + result = tesseract.doOCR(bufferedImage); + } catch (TesseractException e) { + throw new RuntimeException(e); + } + + // 清理和格式化结果 + String cleanedResult = cleanOcrResult(result); + + System.out.println("\n=== 识别结果 ==="); + System.out.println(cleanedResult); + + return cleanedResult; + } + + /** + * 灰度化图像(直接处理 BufferedImage) + * @param image 原始图片 + * @return 灰度图 + */ + private static BufferedImage preprocessImage(BufferedImage image) { + BufferedImage grayImage = new BufferedImage( + image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY); + + Graphics2D g2d = grayImage.createGraphics(); + g2d.drawImage(image, 0, 0, null); + g2d.dispose(); + + return grayImage; + } + + /** + * 清理OCR识别结果 + * @param result 原始识别结果 + * @return 清理后的结果 + */ + private static String cleanOcrResult(String result) { + if (result == null || result.trim().isEmpty()) { + return ""; + } + + // 替换常见的OCR错误 + String cleaned = result + // 修复常见的OCR错误 + .replaceAll("英声租", "产品名称") + .replaceAll("库咤埕号", "产品型号") + .replaceAll("产晓序列号", "产品序列号") + .replaceAll("购买纳证缉号", "购买凭证编号") + .replaceAll("质保条孰", "质保条款") + .replaceAll("本亢命", "本产品") + .replaceAll("质僚服务", "质保服务") + .replaceAll("质保朝内", "质保期内") + .replaceAll("团素", "因素") + .replaceAll("质量闰题", "质量问题") + .replaceAll("免贵维修", "免费维修") + .replaceAll("更挺服", "更换服") + .replaceAll("不包挂", "不包括") + .replaceAll("溢用", "滥用") + .replaceAll("探环", "损坏") + .replaceAll("取葛保管", "妥善保管") + .replaceAll("雷凭吊证明", "需凭此证明") + .replaceAll("客户信恩", "客户信息") + .replaceAll("姆钗", "姓名") + .replaceAll("联系电语", "联系电话") + .replaceAll("电子邹件", "电子邮件") + .replaceAll("地抛", "地址") + .replaceAll("摒权代表", "授权代表") + .replaceAll("介为示例", "仅为示例") + .replaceAll("根揪实际情况", "根据实际情况") + .replaceAll("调教", "调整") + // 移除多余的空格 + .trim(); + + return cleaned; + } +} diff --git a/klp-wms/src/main/java/com/klp/service/impl/WmsOrderDetailServiceImpl.java b/klp-wms/src/main/java/com/klp/service/impl/WmsOrderDetailServiceImpl.java index 2ae59e15..a9d81beb 100644 --- a/klp-wms/src/main/java/com/klp/service/impl/WmsOrderDetailServiceImpl.java +++ b/klp-wms/src/main/java/com/klp/service/impl/WmsOrderDetailServiceImpl.java @@ -6,7 +6,6 @@ import com.klp.common.core.domain.PageQuery; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.klp.common.utils.StringUtils; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; diff --git a/klp-wms/src/main/resources/mapper/klp/WmsPurchasePlanDetailMapper.xml b/klp-wms/src/main/resources/mapper/klp/WmsPurchasePlanDetailMapper.xml index 619cf68f..fba36565 100644 --- a/klp-wms/src/main/resources/mapper/klp/WmsPurchasePlanDetailMapper.xml +++ b/klp-wms/src/main/resources/mapper/klp/WmsPurchasePlanDetailMapper.xml @@ -11,6 +11,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + diff --git a/klp-wms/src/main/resources/mapper/klp/WmsStockMapper.xml b/klp-wms/src/main/resources/mapper/klp/WmsStockMapper.xml index 9609c64c..18065e71 100644 --- a/klp-wms/src/main/resources/mapper/klp/WmsStockMapper.xml +++ b/klp-wms/src/main/resources/mapper/klp/WmsStockMapper.xml @@ -29,6 +29,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" diff --git a/klp-wms/src/main/resources/tessdata/chi_sim.traineddata b/klp-wms/src/main/resources/tessdata/chi_sim.traineddata deleted file mode 100644 index da7fa49d..00000000 Binary files a/klp-wms/src/main/resources/tessdata/chi_sim.traineddata and /dev/null differ diff --git a/klp-wms/src/main/resources/tessdata/chi_sim_vert.traineddata b/klp-wms/src/main/resources/tessdata/chi_sim_vert.traineddata deleted file mode 100644 index 851996ca..00000000 Binary files a/klp-wms/src/main/resources/tessdata/chi_sim_vert.traineddata and /dev/null differ