前端使用html+css+js,后端使用Spring Boot,数据库使用mysql,识别算法有两个,一个是使用百度OCR接口,一个是自己写一个python,用flask包装。
其中百度OCR接口可以去免费申请,然后把appid、apikey、secretKey填入application.properties即可
application.properties
spring.application.name=demo# baidu_ocr_config
baidu.ocr.appid=your
baidu.ocr.apiKey=your
baidu.ocr.secretKey=your# myself_ocr_model_config
myself.model.url=your flaskspring.thymeleaf.cache= false# database
server.port=8080
spring.datasource.url=jdbc:mysql://localhost:3306/test?characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver#JPA
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect# application.properties
spring.web.resources.static-locations=classpath:/static/,classpath:/demo/static/
数据库
0. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.2</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>demo</artifactId><version>0.0.1-SNAPSHOT</version><name>demo</name><description>demo</description><url/><licenses><license/></licenses><developers><developer/></developers><scm><connection/><developerConnection/><tag/><url/></scm><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- okhttp --><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>4.12.0</version></dependency><!-- 引入Lombok依赖 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- 百度人工智能依赖 --><!-- https://mvnrepository.com/artifact/com.baidu.aip/java-sdk --><dependency><groupId>com.baidu.aip</groupId><artifactId>java-sdk</artifactId><version>4.11.3</version></dependency><!-- thymeleaf模板引擎 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!-- MySQL驱动 --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><!-- Spring Data JPA 依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
1. 项目结构
- config为一些配置设置
- controller为控制层
- dao为数据库实体层
- service为服务层
- css保存样式文件
- js保存js文件
- ocrImg是本地保存图片的位置,数据库里指存放图片在本地的地址
2. css代码
2.1 base.css
/* 去除常见标签默认的 margin 和 padding */
* {margin: 0;padding: 0;box-sizing: border-box;
}/* 设置网页统一的字体大小、行高、字体系列相关属性 */
body {font: 16px/1.5 "Microsoft Yahei","Hiragino Sans GB", "Heiti SC", "WenQuanYi Micro Hei", sans-serif;color: #333;background-color: #f5f7f8;
}/* 去除列表默认样式 */
ul,
ol {list-style: none;
}/* 去除默认的倾斜效果 */
em,
i {font-style: normal;
}/* 去除a标签默认下划线,并设置默认文字颜色 */
a {text-decoration: none;color: #333;
}/* 设置img的垂直对齐方式为居中对齐,去除img默认下间隙 */
img {width: 100%;height: 100%;vertical-align: middle;
}/* 去除input默认样式 */
input {border: none;outline: none;color: #333;
}h1,
h2,
h3,
h4,
h5,
h6 {font-weight: 400;
}
2.2 index.css
.container {width: 10rem;height: auto;
}/* 顶部 */
.header {display: flex;padding: 0 .1563rem;width: 10rem;height: .3385rem;align-items: center;background-color: #fff;
}
.header img {width: 30px;height: 30px;
}
.header span {margin-left: .026rem;font-size: .1042rem;
}
/* 功能部分 */
.main {width: 10rem;height: auto;padding: 0 .1563rem;
}
.operation {display: flex;margin: .1563rem 0;
}
.operation input {display: none;
}
.operation button {display: none;
}
.operation label {display: flex;margin-right: .1042rem;width: .7813rem;height: .2604rem;border: .0052rem solid #cbc2c2;border-radius: .0417rem;align-items: center;cursor: pointer;
}
.operation label:hover {background-color: #d1f6ff;
}
.operation label .left {margin-right: .0417rem;padding: .0781rem 0;width: .2083rem;height: .2604rem;text-align: center;
}
.operation label .left svg {width: .1042rem;height: .1042rem;
}
.operation label .right h2 {font-size: .0833rem;font-weight: bold;color: #1296db;
}
.operation label .right h3 {font-size: .0625rem;color: #cbc2c2;
}
/* 主体 */
.function {width: 9.70rem;height: 3.125rem;border-radius: .0938rem;background-color: #fff;overflow: hidden;border: .0052rem solid #cbc2c2;
}
.function .title {display: flex;width: 100%;height: .2604rem;border-bottom: .0052rem solid #cbc2c2;align-items: center;
}
.function .title h2 {width: 50%;padding-left: .1042rem;font-size: .0833rem;font-weight: bold;color: #1f4d77;
}
.function .action {display: flex;width: 100%;height: 2.8646rem;
}
.function .action .source {width: 50%;height: 2.8646rem;border-right: .0052rem solid #cbc2c2;
}
.function .action .source img {width: 100%;height: 100%;object-fit: contain;margin: auto;
}
.function .action .result {width: 50%;height: 2.8646rem;
}
.function .action .result textarea{width: 100%;height: 100%;font-size: .0833rem;border: none;
}/* 历史记录按钮 */
.circle-button {position: absolute;top : 75.5%;right: 0.18rem;bottom: 0.1064rem;width: 0.1583rem;height: 0.1583rem;border: 1px solid black;border-radius: 50%;display: flex;align-items: center;justify-content: center;cursor: pointer;background: transparent;
}.circle-button::before {content: "";display: block;width: 0.1063rem; /* 内部圆的直径 */height: 0.1063rem;border: 1px solid black; /* 细细的黑色边框 */border-radius: 50%; /* 创建圆形效果 */background: transparent; /* 设置背景为透明 */
}.circle-button:hover {border-color: #000; /* 鼠标悬停时保持黑色边框 */
}#modelSelect {background: transparent; /* 可以设置背景颜色 */border: none; /* 移除边框 */padding: 0; /* 移除内边距 */margin: 0; /* 移除外边距 */font-size: .0833rem;font-weight: bold;color: #1f4d77;cursor: pointer; /* 改变鼠标指针 */position: relative; /* 为了定位伪元素 */display: inline-block; /* 使其与周围的元素在同一行显示 *//* 增加宽度以适应文本和箭头 */width: 100px; /* 或者使用您喜欢的宽度 *//* 设置高度,以确保箭头在垂直方向上居中 */height: 2em; /* 或者使用您喜欢的高度 */line-height: 2em; /* 文本垂直居中 */
}/* 加载动画 */
.loading {border: 4px solid rgba(0, 0, 0, 0.1);border-top-color: #1296db;border-radius: 50%;width: 24px;height: 24px;animation: spin 1s linear infinite;position: absolute;top: 50%;left: 74%;transform: translate(-50%, -50%);display: none;
}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }
}
2.3 re.css
.container {display: flex;flex-direction: column;width: 100vw;height: 100vh;
}/* 顶部 */
.header {display: flex;padding: 0 1.563vw;width: 100vw;height: 3.385vw;align-items: center;background-color: #fff;
}
.header img {width: 30px;height: 30px;
}
.header span {margin-left: .26vw;font-size: 1.042vw;
}
/* 功能部分 */
.main {display: flex;flex: 1;width: 100vw;
}
.main .left {width: 10%;background-color: #fff;
}
.main .left .logs {width: 100%;height: 3vw;line-height: 3vw;text-align: center;background-color: #bdd7ee;
}
.main .left .logs span {font-weight: bold;color: #2e75b6;
}
.main .right {width: 90%;height: 100%;overflow-y: scroll;
}
.right .table {width: 100%;height: auto;
}
table
{border-collapse:collapse;
}
table, th, td
{border: 1px solid black;
}
.table .thead {width: 100%;height: 3vw;text-align: center;line-height: 3vw;
}
.table td {position: relative;text-align: center;
}
.table tbody tr {width: 100%;height: 10vw;
}
.table tbody tr:nth-child(2n-1){background-color: #fff;
}
.table tbody td:nth-child(1) {width: 5%;
}
.table tbody tr td:nth-child(2){width: 40%;
}
.table tbody tr td:nth-child(3){width: 30%;
}
.table tbody tr td:nth-child(4){width: 15%;
}
.table tbody tr td:nth-child(5){width: 10%;
}.image-container {position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%); /* 将 image-container 移动到 td 的中心 */width: 470px; /* 或者指定一个宽度 */height: 130px; /* 或者指定一个高度 */overflow: hidden;display: flex;align-items: center;justify-content: center;
}.image-container img {max-width: 100%; /* 图片的最大宽度为容器宽度 */max-height: 100%; /* 图片的最大高度为容器高度 */object-fit: contain; /* 保持图片的比例 */
}
3. JS 代码
3.1 doData.js
javascript">document.addEventListener('DOMContentLoaded', function() {const tableBody = document.getElementById('table-body');if (tableBody === null) {console.error('The element with id "table-body" is not found.');} else {fetch('http://localhost:8080/history').then(response => response.json()).then(data => {//逆序data.sort((a,b) =>b.id-a.id);data.forEach(item => {// 创建一个新的表格行const row = document.createElement('tr');// 创建序号单元格const idCell = document.createElement('td');idCell.textContent = item.id;row.appendChild(idCell);// 创建图片单元格const imageCell = document.createElement('td');const imgElement = document.createElement('img');// 使用 item.imagedata 作为图片的 srcimgElement.src = item.imagedata;imgElement.alt = "Image";// 包裹在 div 中const imageContainer = document.createElement('div');imageContainer.className = 'image-container'; // 添加类名imageContainer.appendChild(imgElement);imageCell.appendChild(imageContainer);row.appendChild(imageCell);// 创建识别结果单元格const textCell = document.createElement('td');textCell.textContent = item.text;row.appendChild(textCell);// 创建识别时间单元格const datetimeCell = document.createElement('td');datetimeCell.textContent = item.datetime;row.appendChild(datetimeCell);// 创建识别模型单元格const modelCell = document.createElement('td');modelCell.textContent = item.modelname;row.appendChild(modelCell);// 将新行添加到表格中tableBody.appendChild(row);});}).catch(error => console.error('Error fetching data:', error));}console.log(tableBody);
});
3.2 flexible.js
javascript">(function flexible (window, document) {var docEl = document.documentElementvar dpr = window.devicePixelRatio || 1// adjust body font sizefunction setBodyFontSize () {if (document.body) {document.body.style.fontSize = (12 * dpr) + 'px'}else {document.addEventListener('DOMContentLoaded', setBodyFontSize)}}setBodyFontSize();// set 1rem = viewWidth / 10function setRemUnit () {var rem = docEl.clientWidth / 10docEl.style.fontSize = rem + 'px'}setRemUnit()// reset rem unit on page resizewindow.addEventListener('resize', setRemUnit)window.addEventListener('pageshow', function (e) {if (e.persisted) {setRemUnit()}})// detect 0.5px supportsif (dpr >= 2) {var fakeBody = document.createElement('body')var testElement = document.createElement('div')testElement.style.border = '.5px solid transparent'fakeBody.appendChild(testElement)docEl.appendChild(fakeBody)if (testElement.offsetHeight === 1) {docEl.classList.add('hairlines')}docEl.removeChild(fakeBody)}}(window, document))
3.3 index.js
javascript">// 预览上传图片
document.getElementById('imageInput').addEventListener('change', function (e) {let file = e.target.files[0];if (file) {let reader = new FileReader();reader.onload = function (e) {let image = new Image();image.src = e.target.result;image.onload = function () {document.getElementById('uploadedImage').src = image.src;document.getElementById('uploadedImage').style.display = 'block';};};reader.readAsDataURL(file);}
});// 当点击识别按钮
document.getElementById('sumbutt').addEventListener('click', function () {let fileInput = document.getElementById('imageInput');if (fileInput.files.length > 0) {let formData = new FormData();formData.append('file', fileInput.files[0]);// 获取用户选择的OCR模型let modelSelect = document.getElementById('modelSelect');let selectedModel = modelSelect.value;showLoadingAnimation();// 调用函数发送文件到服务器进行OCR识别sendFileToServer(formData, selectedModel);} else {alert('请选择一个图片文件!');}
});// 发送文件到服务器进行OCR识别
function sendFileToServer(formData, model) {let url;if (model === 'baidu') {url = 'http://localhost:8080/baiduOcr';} else if (model === 'myModel') {url = 'http://localhost:8080/myModel';}fetch(url, {method: 'POST',body: formData}).then(function (response) {if (!response.ok) {throw new Error('Network response was not ok');}return response.text();}).then(function (text) {hideLoadingAnimation();console.log('OCR Result:', text);displayOcrResult(splitAndProcessText(text));}).catch(function (error) {hideLoadingAnimation();alert('识别失败,请稍后重试');console.error('There has been a problem with your fetch operation:', error);});
}// 将OCR结果显示在页面上
function displayOcrResult(ocrData) {let resultMsgElement = document.getElementById('resultMsg');resultMsgElement.value = ocrData.join('\n');
}// 将从后端收到的字符串分割和处理成数组
function splitAndProcessText(text) {return text.split(/\r?\n/);
}// 显示加载动画
function showLoadingAnimation() {let loading = document.getElementById('loading');loading.style.display = 'block';
}// 隐藏加载动画
function hideLoadingAnimation() {let loading = document.getElementById('loading');loading.style.display = 'none';
}// 点击打开历史记录页面
let circleButton = document.querySelector('.circle-button');
circleButton.addEventListener('click', function() {window.open('re.html', '_blank');
});
4. 前端页面
4.1识别页面
4.1.1 index.html
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="stylesheet" href="../static/css/base.css"><link rel="stylesheet" href="../static/css/index.css"><title>识别页面</title>
</head><body><div class="container"><div class="header"><a href="index.html"><img src="../static/ocrImg/logo.jpg" alt=""><span>OCR文字识别</span></a></div><div class="main"><!-- 操作 --><div class="operation"><label for="imageInput"><div class="left"><svg t="1722243087697" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"p-id="2417" width="200" height="200"><pathd="M839.647708 1016.974416H184.352292a98.358181 98.358181 0 0 1-97.719492-98.358182V107.480583A98.358181 98.358181 0 0 1 184.352292 9.122402h383.213694a97.080802 97.080802 0 0 1 63.868948 24.90889l275.27517 243.979385a95.803423 95.803423 0 0 1 32.573164 72.810602v567.794955a98.358181 98.358181 0 0 1-99.63556 98.358182zM184.352292 74.907419a32.573164 32.573164 0 0 0-32.573164 32.573164v811.135651a32.573164 32.573164 0 0 0 32.573164 32.573164h655.295416a32.573164 32.573164 0 0 0 32.573164-32.573164V350.821279a30.657095 30.657095 0 0 0-10.857722-23.631512l-274.63648-243.979384a35.127922 35.127922 0 0 0-21.715442-8.302964z"fill="#1296db" p-id="2418"></path><pathd="M448.131051 354.653416H288.458679a33.211853 33.211853 0 0 1 0-63.868949H448.131051a33.211853 33.211853 0 1 1 0 63.868949zM667.840235 547.537641H288.458679a32.573164 32.573164 0 0 1 0-63.868949h379.381556a32.573164 32.573164 0 0 1 0 63.868949zM667.840235 741.060556H288.458679a33.211853 33.211853 0 0 1 0-63.868949h379.381556a33.211853 33.211853 0 0 1 0 63.868949zM883.078593 359.124242h-319.344744a32.573164 32.573164 0 0 1-33.211854-32.573164V42.334255a33.211853 33.211853 0 1 1 63.868949 0v251.643659h285.494202a32.573164 32.573164 0 1 1 0 63.868949z"fill="#1296db" p-id="2419"></path></svg></div><div class="right"><h2>上传图片</h2><h3>JPG,PNG,JPEG</h3></div></label><input type="file" id="imageInput" accept="image/png,image/jpeg,image/jpg" onchange="javascript language-javascript">uploadImage(this)"><label for="sumbutt"><div class="left"><svg t="1722300478940" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"p-id="2849" width="200" height="200"><pathd="M974.72 224L798.08 47.36a52.928 52.928 0 0 0-75.52 0l-603.52 603.52c-6.4 6.4-10.88 14.72-13.44 23.04L35.84 924.16c-5.12 18.56 0 39.04 14.08 52.48 10.24 10.24 23.68 15.36 37.12 15.36 5.12 0 10.24-0.64 15.36-1.92l246.4-73.6c8.32-2.56 16-7.04 22.4-13.44l603.52-603.52c21.12-20.48 21.12-54.4 0-75.52z m-113.28 37.76l-34.56 34.56-101.12-101.12 34.56-34.56 101.12 101.12z m-517.76 518.4L242.56 678.4l407.68-407.68 101.12 101.12-407.68 408.32z m-155.52-5.12l60.16 60.16-83.84 24.96 23.68-85.12z"fill="#1296db" p-id="2850"></path></svg></div><div class="right"><h2>开始识别</h2></div></label><button id="sumbutt" type="submit">识别</button></div><!-- 功能部分 --><div class="function"><div class="title"><h2>识别图片</h2><select id="modelSelect"><option value="baidu">百度OCR模型</option><option value="myModel">自建OCR模型</option></select></div><div class="action"><!-- 上传图片展示 --><div class="source"><img id="uploadedImage"></div><!-- 识别结果展示 --><div class="result"><textarea name="" id="resultMsg" cols="30" rows="10"></textarea><div class="circle-button"></div><div class="loading" id="loading"></div></div></div></div></div></div>
</body><script src="../static/js/index.js"></script>
<script src="../static/js/flexible.js"></script></html>
4.2 历史记录页面
4.2.1 re.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>历史记录</title><link rel="stylesheet" href="../static/css/base.css"><link rel="stylesheet" href="../static/css/re.css"><script src="../static/js/doData.js"></script>
</head>
<body>
<div class="container"><div class="header"><a href="index.html"><img src="../static/ocrImg/logo.jpg" alt=""><span>OCR文字识别</span></a></div><div class="main"><div class="left"><div class="logs"><span>识别记录</span></div></div><div class="right"><table class="table"><thead class="thead"><tr><th>序号</th><th>图片</th><th>识别结果</th><th>识别时间</th><th>识别模型</th></tr></thead><tbody id="table-body"></tbody></table></div></div>
</div></body>
</html>
5. config文件夹
5.1 baiduOcrProperties
java">package com.example.demo.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;@Data
@Configuration
@ConfigurationProperties(prefix = "baidu.ocr")
public class baiduOcrProperties {// 百度OCR的App IDprivate String appId;// 百度OCR的API Keyprivate String apiKey;// 百度OCR的Secret Keyprivate String secretKey;
}
5.2 CorsConfiguration
java">package com.example.demo.config;import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** 配置跨域请求*/
@Configuration
public class CorsConfiguration implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOriginPatterns("*").allowCredentials(true).allowedMethods("GET", "POST", "DELETE", "PUT").maxAge(3600);}
}
5.3 myselfModelProperties
java">package com.example.demo.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;@Data
@Configuration
@ConfigurationProperties(prefix = "myself.model")
public class myselfModelProperties {// 识别模型地址private String url;
}
6.controller文件夹
6.1 ocrController
java">package com.example.demo.controller;import com.example.demo.dao.imageTable;
import com.example.demo.service.imageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.util.List;@RestController
public class ocrController {private final imageService imageService;@Autowiredpublic ocrController(imageService imageService) {this.imageService = imageService;}/*** 获取所有OCR识别记录。** @return 所有OCR识别记录。*/@GetMapping("/history")public List<imageTable> getAllImages() {return imageService.getAllImages();}/*** 使用自建OCR模型识别图片** @param file 用户上传的文件* @return OCR识别结果。* @throws IOException 读取文件时发生错误*/@PostMapping("/myModel")public ResponseEntity<String> myModel(MultipartFile file) throws IOException {String saveImgName= imageService.loadAndSaveImage(file);try {List<String> ocrResult = imageService.performMyselfOcrModel(file);//执行自建OCR识别// 将 List<String> 转换为一个单一的字符串StringBuilder resultString = new StringBuilder();for (String line : ocrResult) {resultString.append(line).append("\n");}String modelName="OcrNetV1";imageService.saveOcrData(modelName,saveImgName, resultString.toString());return ResponseEntity.ok(resultString.toString());} catch (Exception e) {e.printStackTrace();return ResponseEntity.status(500).body("Doing ocr is error:" + e.getMessage());// 返回一个带有错误信息的失败响应}}/*** 使用百度ocr处理图片文字识别请求。** @param file 用户上传的文件* @return 识别结果。* @throws IOException 如果读取文件时发生错误。*/@PostMapping(value = "/baiduOcr")public ResponseEntity<String> ocr(MultipartFile file) throws IOException {String saveImgName=imageService.loadAndSaveImage(file);try {List<String> ocrResult = imageService.performBaiduOcr(file);//执行百度OCR识别// 将 List<String> 转换为一个单一的字符串StringBuilder resultString = new StringBuilder();for (String line : ocrResult) {resultString.append(line).append("\n");}System.out.println(saveImgName);String modelName="BaiDuOcr";imageService.saveOcrData(modelName,saveImgName, resultString.toString());return ResponseEntity.ok(resultString.toString());} catch (Exception e) {e.printStackTrace();return ResponseEntity.status(500).body("Doing ocr is error:" + e.getMessage());// 返回一个带有错误信息的失败响应}}
}
7. dao文件夹
7.1 imageTable
java">package com.example.demo.dao;import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;import static jakarta.persistence.GenerationType.IDENTITY;@Getter
@Setter
@Entity
@Table(name = "OCRHISTORY")
public class imageTable {@Id@GeneratedValue(strategy = IDENTITY)@Column(name = "id", nullable = false)private Integer id;@Column(name = "modelname")private String modelname;@Column(name = "imagedata")private String imagedata;@Column(name = "datetime")private String datetime;@Column(name = "text")private String text;}
7.2 imageTableRepository
java">package com.example.demo.dao;import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;@Repository
public interface imageTableRepository extends JpaRepository<imageTable, Integer> {}
8.service文件夹
8.1 imageService
java">package com.example.demo.service;import com.example.demo.dao.imageTable;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.util.List;public interface imageService {List<imageTable> getAllImages();//获取所有图片void saveOcrData(String modelName,String imagePath, String ocrResult);//保存图片识别结果String getFileContentAsBase64(MultipartFile file, boolean urlEncode) throws IOException;//将文件转换为Base64编码List<String> performBaiduOcr(MultipartFile file) throws Exception;//执行百度OCR识别List<String> performMyselfOcrModel(MultipartFile file) throws Exception;//执行自定义OCR识别String loadAndSaveImage(MultipartFile file) throws IOException;//加载并保存图片}
8.2 imageServiceImpl
java">package com.example.demo.service;import com.example.demo.dao.imageTableRepository;
import com.example.demo.dao.imageTable;
import com.example.demo.config.baiduOcrProperties;
import com.example.demo.config.myselfModelProperties;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.List;@Service
public class imageServiceImpl implements imageService{private final baiduOcrProperties baiduOcrProperties;static final OkHttpClient HTTP_CLIENT = new OkHttpClient().newBuilder().build();private final imageTableRepository imageTableRepository;private final myselfModelProperties myselfModelProperties;// 初始化@Autowiredpublic imageServiceImpl(baiduOcrProperties baiduOcrProperties, imageTableRepository imageTableRepository, com.example.demo.config.myselfModelProperties myselfModelProperties) {this.baiduOcrProperties = baiduOcrProperties;this.imageTableRepository = imageTableRepository;this.myselfModelProperties = myselfModelProperties;}/*** 导入图片、保存图片到本地** @param file 用户上传的文件* @return 图片保存后的文件名* @throws IOException 读取文件时发生错误*/public String loadAndSaveImage(MultipartFile file) throws IOException {System.out.println("<-------------->");System.out.println("Image upload ok!");System.out.println("<-------------->");System.out.println(" ");String contentType = file.getContentType();String[] parts = null;//格式为:image/pngif (contentType != null) {parts = contentType.split("/");}else{System.out.println("<-------------->");System.out.println("Parts is null!");System.out.println("<-------------->");System.out.println(" ");}String fileExtension = null;//文件后缀if (parts != null) {fileExtension = parts[parts.length - 1];}else{System.out.println("<-------------->");System.out.println("Parts is null!");System.out.println("<-------------->");System.out.println(" ");}// 保存图片到本地Path path = Paths.get("D:/code/java/demo/src/main/resources/static/ocrImg");Files.createDirectories(path);String fileName = "image-" + System.currentTimeMillis() + '.'+fileExtension;String saveImgName="/demo/static/ocrImg/"+fileName;Files.copy(file.getInputStream(), path.resolve(fileName));Path imagePath = path.resolve(fileName);// 图片的本地路径// 检查文件是否存在if (Files.exists(imagePath)) {// 如果文件大小为0,说明文件可能有问题long fileSize = Files.size(imagePath);if (fileSize == 0) {System.err.println("The saved file is empty.");}} else {System.err.println("The file was not saved.");}return saveImgName;}/*** 获取所有图片的识别记录** @return 所有图片的识别记录*/@Overridepublic List<imageTable> getAllImages() {return imageTableRepository.findAll();}/*** 将识别记录(路径、识别时间、识别结果)保存到数据库** @param imagePath 图片在本地的存储路径* @param ocrResult 图片的识别记录*/public void saveOcrData(String modelName,String imagePath, String ocrResult) {Date now = new Date();SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 创建 imagesTable 实例imageTable ocrhistory = new imageTable();ocrhistory.setModelname(modelName);ocrhistory.setDatetime(dateFormat.format(now));ocrhistory.setImagedata(imagePath);ocrhistory.setText(ocrResult);// 保存到数据库imageTableRepository.save(ocrhistory);}/*** 获取文件base64编码** @param file 文件* @param urlEncode 如果Content-Type是application/x-www-form-urlencoded时,传true* @return base64编码信息,不带文件头* @throws IOException IO异常*/public String getFileContentAsBase64(MultipartFile file, boolean urlEncode) throws IOException {if (file == null){return "";}else{byte[] buf = file.getBytes();String base64 = Base64.getEncoder().encodeToString(buf);if (urlEncode) {base64 = URLEncoder.encode(base64, StandardCharsets.UTF_8);}return base64;}}/*** 连接自建OCR模型识别系统** @param fileBase64 base64编码信息,不带文件头* @return 识别结果*/public String MyselfOcrModel(String fileBase64) {String re="";String modelUrl = myselfModelProperties.getUrl();HttpHeaders headers = new HttpHeaders();headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON);String requestBody = "{\"image\": \"" + fileBase64 + "\"}";HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);RestTemplate restTemplate = new RestTemplate();ResponseEntity<String> response = restTemplate.postForEntity(modelUrl, entity, String.class);if(response.getBody() != null){re= response.getBody();}return re;}/*** 执行自建OCR模型识别** @param file 需要识别的图片* @return 识别结果* @throws Exception 异常*/@Overridepublic List<String> performMyselfOcrModel(MultipartFile file) throws Exception {List<String> wordsList = new ArrayList<>();String fileBase64;fileBase64 = getFileContentAsBase64(file, false);if (fileBase64.isEmpty()){wordsList.add("File is NULL");return wordsList;}String res_json=MyselfOcrModel(fileBase64);return JsonToWordsList(wordsList, res_json);}/*** 从百度OCR服务获取Access Token。** @return Access Token,用于身份验证。* @throws IOException 如果在获取Access Token过程中出现IO错误。*/public String getAccessToken() throws IOException {String acc="";String apiKey=baiduOcrProperties.getApiKey();String secretKey=baiduOcrProperties.getSecretKey();MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");RequestBody body = RequestBody.create(mediaType, "grant_type=client_credentials&client_id=" + apiKey+ "&client_secret=" + secretKey);Request request = new Request.Builder().url("https://aip.baidubce.com/oauth/2.0/token").method("POST", body).addHeader("Content-Type", "application/x-www-form-urlencoded").build();Response response = HTTP_CLIENT.newCall(request).execute();if (response.body() != null) {acc=response.body().string();}return new JSONObject(acc).getString("access_token");}/*** 连接百度OCR识别系统** @param fileBase64 base64编码信息,不带文件头* @return 百度OCR识别结果* @throws IOException IO异常*/public String baiduOcr(String fileBase64) throws IOException{String res = "";System.out.println(fileBase64);MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");RequestBody body = RequestBody.create(mediaType, "image="+fileBase64+"&detect_direction=false¶graph=false&probability=false");Request request = new Request.Builder().url("https://aip.baidubce.com/rest/2.0/ocr/v1/accurate_basic?access_token=" + getAccessToken()).method("POST", body).addHeader("Content-Type", "application/x-www-form-urlencoded").addHeader("Accept", "application/json").build();Response response = HTTP_CLIENT.newCall(request).execute();if (response.body() != null) {res=response.body().string();}return res;}/*** 执行百度OCR识别操作。** @param file 需要进行OCR识别的文件。* @return 识别到的文本列表。* @throws Exception 如果识别过程中出现错误,则抛出异常。*/public List<String> performBaiduOcr(MultipartFile file) throws Exception {List<String> wordsList = new ArrayList<>();String fileBase64;fileBase64 = getFileContentAsBase64(file, true);if (fileBase64.isEmpty()){wordsList.add("File is NULL");return wordsList;}String res_json=baiduOcr(fileBase64);return JsonToWordsList(wordsList, res_json);}/**** 将json数据转换为wordsList** @param wordsList 存储识别结果的列表* @param res_json 识别模型回传的Json数据* @return wordsList*/private List<String> JsonToWordsList(List<String> wordsList, String res_json) {try {ObjectMapper mapper = new ObjectMapper();JsonNode rootNode = mapper.readTree(res_json);JsonNode wordsResultNode = rootNode.get("words_result");for (JsonNode wordNode : wordsResultNode) {wordsList.add(wordNode.get("words").asText());}// 输出结果System.out.println("------wordsList------");for (String word : wordsList) {System.out.println(word);}System.out.println("------wordsList------");} catch (IOException e) {e.printStackTrace();}return wordsList;}}