Merge remote-tracking branch 'origin/0.8.X' into 0.8.X
# Conflicts: # klp-admin/src/main/resources/application.yml
This commit is contained in:
@@ -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生产环境
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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 目录本身
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<RecognizeTextVo> recognize(@RequestBody Map<String, String> request) {
|
||||
String imgUrl = request.get("imgUrl");
|
||||
String text = iTesseractOcrService.recognizeText(imgUrl);
|
||||
return R.ok(new RecognizeTextVo(text));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询采购计划主列表
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.klp.domain;
|
||||
|
||||
import jdk.jfr.DataAmount;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,6 +45,10 @@ public class WmsPurchasePlanDetail extends BaseEntity {
|
||||
* 单位
|
||||
*/
|
||||
private String unit;
|
||||
/**
|
||||
* 附件
|
||||
*/
|
||||
private String annex;
|
||||
/**
|
||||
* 状态( 0=新建,1=在途,2=到货,3=待审核,4=采购完成)
|
||||
*/
|
||||
|
||||
@@ -50,6 +50,11 @@ public class WmsPurchasePlanDetailBo extends BaseEntity {
|
||||
*/
|
||||
private String unit;
|
||||
|
||||
/**
|
||||
* 附件
|
||||
*/
|
||||
private String annex;
|
||||
|
||||
/**
|
||||
* 状态( 0=新建,1=在途,2=到货,3=待审核,4=采购完成)
|
||||
*/
|
||||
|
||||
19
klp-wms/src/main/java/com/klp/domain/vo/RecognizeTextVo.java
Normal file
19
klp-wms/src/main/java/com/klp/domain/vo/RecognizeTextVo.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,12 @@ public class WmsPurchasePlanDetailVo {
|
||||
@ExcelProperty(value = "单位")
|
||||
private String unit;
|
||||
|
||||
/**
|
||||
* 附件
|
||||
*/
|
||||
@ExcelProperty(value = "附件")
|
||||
private String annex;
|
||||
|
||||
/**
|
||||
* 状态( 0=新建,1=在途,2=到货,3=待审核,4=采购完成)
|
||||
*/
|
||||
|
||||
@@ -80,5 +80,10 @@ public class WmsStockVo {
|
||||
@ExcelProperty(value = "备注")
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 仓库/库区名称
|
||||
*/
|
||||
@ExcelProperty(value = "仓库/库区名称")
|
||||
private String warehouseName;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.klp.service;
|
||||
|
||||
public interface ITesseractOcrService {
|
||||
|
||||
/**
|
||||
* 识别网络图片
|
||||
* @param imageUrl 图片URL
|
||||
* @return 识别结果
|
||||
*/
|
||||
String recognizeText(String imageUrl);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -11,6 +11,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="owner" column="owner"/>
|
||||
<result property="quantity" column="quantity"/>
|
||||
<result property="unit" column="unit"/>
|
||||
<result property="annex" column="annex"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="remark" column="remark"/>
|
||||
<result property="delFlag" column="del_flag"/>
|
||||
|
||||
@@ -29,6 +29,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<select id="selectVoPagePlus" resultType="com.klp.domain.vo.WmsStockVo">
|
||||
SELECT
|
||||
s.*,
|
||||
w.warehouse_name,
|
||||
CASE
|
||||
WHEN s.item_type = 'product' THEN p.product_name
|
||||
WHEN s.item_type = 'raw_material' THEN r.raw_material_name
|
||||
@@ -42,6 +43,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
FROM wms_stock s
|
||||
LEFT JOIN wms_product p ON s.item_type = 'product' AND s.item_id = p.product_id
|
||||
LEFT JOIN wms_raw_material r ON s.item_type = 'raw_material' AND s.item_id = r.raw_material_id
|
||||
left join wms_warehouse w on s.warehouse_id = w.warehouse_id
|
||||
${ew.customSqlSegment}
|
||||
</select>
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user