feat(ocr): 集成 PaddleOCR 服务并优化 OCR 系统
- 新增 PaddleOCR 本地高精度 OCR 服务支持,包括 Dockerfile、API 服务和 provider 实现 - 在 docker-compose 中集成 RapidOCR 和 PaddleOCR 服务,并配置健康检查 - 优化后端 API 路由前缀,移除 `/api` 以简化代理配置 - 更新 Nginx 配置以正确传递请求头和代理 WebSocket 连接 - 在前端设置页面添加 PaddleOCR 和 RapidOCR 的测试与配置选项 - 修复后端 Dockerfile 以支持 Python 原生模块构建 - 更新 OCR 设置指南,反映当前服务状态和部署方式 - 添加上传文件调试日志和权限设置
This commit is contained in:
@@ -16,21 +16,34 @@ server {
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
|
||||
# API proxy to backend
|
||||
location /api {
|
||||
proxy_pass http://backend:13057;
|
||||
location /api/ {
|
||||
proxy_pass http://backend:13057/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
|
||||
# Pass ALL request headers to backend
|
||||
proxy_pass_request_headers on;
|
||||
|
||||
# Set standard proxy headers
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# Explicitly pass authentication headers
|
||||
proxy_set_header Authorization $http_authorization;
|
||||
|
||||
# WebSocket support
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
# Bypass cache
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_no_cache $http_upgrade;
|
||||
}
|
||||
|
||||
# Upload files proxy to backend
|
||||
location /uploads {
|
||||
proxy_pass http://backend:13057/uploads;
|
||||
location /uploads/ {
|
||||
proxy_pass http://backend:13057/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
|
||||
35
frontend/package-lock.json
generated
35
frontend/package-lock.json
generated
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@playwright/test": "^1.40.0",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
@@ -1328,19 +1328,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
|
||||
"integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
|
||||
"version": "1.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.0.tgz",
|
||||
"integrity": "sha512-PdW+kn4eV99iP5gxWNSDQCbhMaDVej+RXL5xr6t04nbKLCBwYtA046t7ofoczHOm8u6c+45hpDKQVZqtqwkeQg==",
|
||||
"deprecated": "Please update to the latest version of Playwright to test up-to-date browsers.",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.58.2"
|
||||
"playwright": "1.40.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@polka/url": {
|
||||
@@ -5200,35 +5200,33 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
|
||||
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
|
||||
"version": "1.40.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.0.tgz",
|
||||
"integrity": "sha512-gyHAgQjiDf1m34Xpwzaqb76KgfzYrhK7iih+2IzcOCoZWr/8ZqmdBw+t0RU85ZmfJMgtgAiNtBQ/KS2325INXw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.58.2"
|
||||
"playwright-core": "1.40.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=16"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
|
||||
"integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
|
||||
"version": "1.40.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.0.tgz",
|
||||
"integrity": "sha512-fvKewVJpGeca8t0ipM56jkVSU6Eo0RmFvQ/MaCQNDYm+sdvKkMBBWTE1FdeMqIdumRaXXjZChWHvIzCGM/tA/Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright/node_modules/fsevents": {
|
||||
@@ -5237,7 +5235,6 @@
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@playwright/test": "^1.40.0",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Settings, Save, CheckCircle, XCircle, Eye, EyeOff, Server, Globe, Datab
|
||||
|
||||
// 从环境变量或 localStorage 获取 API 地址
|
||||
const getDefaultApiUrl = () => {
|
||||
return import.meta.env.VITE_API_URL || localStorage.getItem('api_base_url') || '/api';
|
||||
return import.meta.env.VITE_API_URL || localStorage.getItem('api_base_url') || '/';
|
||||
};
|
||||
|
||||
type ApiConfig = {
|
||||
@@ -14,13 +14,14 @@ type ApiConfig = {
|
||||
};
|
||||
|
||||
type OCRConfig = {
|
||||
provider: 'auto' | 'tesseract' | 'baidu' | 'tencent' | 'rapidocr';
|
||||
provider: 'auto' | 'tesseract' | 'baidu' | 'tencent' | 'rapidocr' | 'paddleocr';
|
||||
confidenceThreshold: number;
|
||||
baiduApiKey: string;
|
||||
baiduSecretKey: string;
|
||||
tencentSecretId: string;
|
||||
tencentSecretKey: string;
|
||||
rapidocrUrl: string;
|
||||
paddleocrUrl: string;
|
||||
};
|
||||
|
||||
type AIConfig = {
|
||||
@@ -62,7 +63,8 @@ const defaultOCRConfig: OCRConfig = {
|
||||
baiduSecretKey: '',
|
||||
tencentSecretId: '',
|
||||
tencentSecretKey: '',
|
||||
rapidocrUrl: 'http://localhost:8080',
|
||||
rapidocrUrl: 'http://localhost:13058',
|
||||
paddleocrUrl: 'http://localhost:13059',
|
||||
};
|
||||
|
||||
const defaultAIConfig: AIConfig = {
|
||||
@@ -504,6 +506,7 @@ export default function SettingsPage() {
|
||||
<option value="auto">自动选择</option>
|
||||
<option value="tesseract">Tesseract.js (本地)</option>
|
||||
<option value="rapidocr">RapidOCR (本地服务)</option>
|
||||
<option value="paddleocr">PaddleOCR (本地高精度)</option>
|
||||
<option value="baidu">百度 OCR (云端)</option>
|
||||
<option value="tencent">腾讯 OCR (云端)</option>
|
||||
</select>
|
||||
@@ -640,6 +643,45 @@ export default function SettingsPage() {
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card title="PaddleOCR" variant="bordered">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
服务地址
|
||||
</label>
|
||||
<Input
|
||||
value={ocrConfig.paddleocrUrl}
|
||||
onChange={(e) => updateOcrConfig('paddleocrUrl', e.target.value)}
|
||||
placeholder="http://localhost:8866"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs text-gray-600">
|
||||
特点: 免费、离线、高精度、多语言支持 (百度开源)
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
启动: <code className="bg-gray-100 px-1 rounded">docker run -p 8866:8866 987846/paddleocr:latest</code>
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => handleTest('paddleocr')}
|
||||
loading={testing === 'paddleocr'}
|
||||
>
|
||||
测试
|
||||
</Button>
|
||||
</div>
|
||||
{testResults.paddleocr && (
|
||||
<div className={`flex items-center gap-2 text-sm ${testResults.paddleocr.success ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{testResults.paddleocr.success ? <CheckCircle className="h-4 w-4" /> : <XCircle className="h-4 w-4" />}
|
||||
{testResults.paddleocr.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user