jiachenlong/docs/IMAGE_PROCESSING_FLOW.md

417 lines
8.7 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 图片处理流程文档
**版本**: v1.0.0
**更新日期**: 2026-03-16
**作者**: 菜鸟小 D 🤖
---
## 📊 完整流程图
```
用户上传图片
[1] 前端上传组件
[2] 后端接收验证
[3] 文件命名处理
[4] 保存到服务器
[5] 数据库记录
[6] 返回图片 URL
```
---
## 1⃣ 前端上传组件
### 上传页面
**文件**: `frontend/src/pages/Add.jsx`
**上传逻辑**:
```jsx
// 选择图片后自动上传
const handleImageSelect = async (e) => {
const file = e.target.files[0]
if (!file) return
const formData = new FormData()
formData.append('file', file)
formData.append('collection_id', collectionId)
const res = await fetch('/api/ocr/recognize', {
method: 'POST',
body: formData
})
const data = await res.json()
// 处理 OCR 识别结果
}
```
### 图片显示
**文件**: `frontend/src/pages/Detail.jsx`
**显示逻辑**:
```jsx
<img
src={`/uploads/${img.path}`}
alt={img.originalName}
onError={(e) => {
// 加载失败显示"无图片"占位符
e.target.style.display = 'none';
e.target.parentElement.innerHTML = '<div>无图片</div>';
}}
/>
```
---
## 2⃣ 后端接收验证
### API 端点
**文件**: `backend/app/routers/collections.py`
**路由**: `POST /api/collections/upload-image`
### 验证流程
```python
@router.post("/upload-image")
async def upload_image(
collection_id: str = None,
file: UploadFile = File(...),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
```
### 验证步骤
1. **验证藏品是否存在**
```python
collection = db.query(Collection).filter(
Collection.f99_90_id == collection_id
).first()
if not collection:
raise HTTPException(status_code=404, detail="E00033: 藏品不存在")
```
2. **获取用户信息**
```python
owner = db.query(User).filter(
User.f99_90_id == collection.f99_91_user_id
).first()
username = owner.f01_01_name if owner else "unknown"
```
3. **获取藏品信息**
```python
code = collection.f01_02_code or "0000"
prefix_serial = collection.f02_10_prefix_serial or ""
```
4. **验证文件类型**
```python
if not file.content_type.startswith('image/'):
raise HTTPException(status_code=400,
detail="E00038: 只能上传图片文件")
```
5. **验证文件大小**
```python
file_size = len(content)
if file_size > 10 * 1024 * 1024: # 10MB
raise HTTPException(status_code=400,
detail=f"图片大小不能超过 10MB")
```
---
## 3⃣ 文件命名处理
### 命名规则
**格式**: `用户名 - 藏品编号 - 冠字号。扩展名`
**示例**:
- `admin-0001-J051963351.jpeg`
- `admin-0002-J035161361.JPG`
- `testuser-0015.jpeg` (无冠字号)
### 命名代码
```python
# 清理特殊字符,只保留字母、数字、中文、横杠
import re
clean_username = re.sub(r'[^\w\u4e00-\u9fff\-]', '', username)
clean_serial = re.sub(r'[^\w\u4e00-\u9fff\-]', '', prefix_serial)
# 生成文件名
file_extension = file.filename.split('.')[-1] if '.' in file.filename else 'jpg'
if clean_serial:
filename = f"{clean_username}-{code}-{clean_serial}.{file_extension}"
else:
filename = f"{clean_username}-{code}.{file_extension}"
```
### 避免重名
```python
# 如果文件已存在,添加时间戳
file_path = os.path.join(upload_dir, filename)
if os.path.exists(file_path):
import time
timestamp = int(time.time())
base_name = filename.rsplit('.', 1)[0]
filename = f"{base_name}-{timestamp}.{file_extension}"
file_path = os.path.join(upload_dir, filename)
```
---
## 4⃣ 保存到服务器
### 存储路径
**目录**: `backend/uploads/collections/`
**完整路径**: `/opt/jiachenlong-backend/uploads/collections/`
### 保存代码
```python
# 创建上传目录
upload_dir = "uploads/collections"
os.makedirs(upload_dir, exist_ok=True)
# 保存文件
with open(file_path, "wb") as buffer:
buffer.write(content)
```
### 文件权限
- **所有者**: root
- **权限**: 644 (rw-r--r--)
- **组**: root
---
## 5⃣ 数据库记录
### 数据表
**表名**: `collection_images`
### 表结构
```sql
CREATE TABLE collection_images (
id VARCHAR(36) PRIMARY KEY, -- UUID
collection_id VARCHAR(36), -- 关联藏品 ID
filename VARCHAR(255), -- 文件名
original_name VARCHAR(255), -- 原始文件名
path VARCHAR(500), -- 存储路径
created_at TIMESTAMP DEFAULT NOW() -- 创建时间
);
```
### 插入记录
```python
from app.models.models import CollectionImage
import uuid
image = CollectionImage(
id=str(uuid.uuid4()),
collection_id=collection_id,
filename=filename,
original_name=file.filename,
path=file_path
)
db.add(image)
db.commit()
db.refresh(image)
```
### 返回数据
```python
return {
"message": "上传成功",
"image_id": image.id,
"filename": filename
}
```
---
## 6⃣ 图片访问
### Nginx 代理配置
**文件**: `/etc/nginx/conf.d/jiachenlong.conf`
```nginx
# 图片上传文件代理
location /uploads {
proxy_pass http://47.110.37.129:3000/uploads;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 20M;
}
```
### 访问 URL 格式
```
http://8.149.137.26/uploads/collections/admin-0001-J051963351.jpeg
```
### 后端静态文件服务
**文件**: `backend/app/main.py`
```python
# 挂载静态文件目录(图片上传)
uploads_dir = "uploads"
os.makedirs(uploads_dir, exist_ok=True)
app.mount("/uploads", StaticFiles(directory=uploads_dir), name="uploads")
```
---
## 🔍 OCR 识别流程
### API 端点
**路由**: `POST /api/ocr/recognize`
**文件**: `backend/app/routers/ocr.py`
### 识别步骤
1. **读取图片并转 Base64**
```python
image_data = await image.read()
image_base64 = base64.b64encode(image_data).decode('utf-8')
```
2. **调用阿里云 DashScope API**
```python
payload = {
"model": "qwen-vl-max",
"messages": [{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_base64}"}},
{"type": "text", "text": PROFESSIONAL_PROMPT}
]
}]
}
```
3. **提取识别结果**
```python
def extract_fields(text: str) -> dict:
patterns = {
'version': r'✅.*?2.*?发行版别.*?[:]\s*(.+?)(?:\n|$)',
'prefix_serial': r'✅.*?6.*?冠字序号.*?[:]\s*(.+?)(?:\n|$)',
'grading_score': r'✅.*?8.*?评级分数.*?[:]\s*(.+?)(?:\n|$)',
# ... 更多字段
}
```
4. **返回结构化数据**
```python
return {
"success": True,
"text": text_content,
"fields": fields
}
```
---
## 📋 完整示例
### 用户上传流程
1. **用户选择图片** → 前端显示预览
2. **点击上传** → 发送到 `/api/ocr/recognize`
3. **OCR 识别** → 提取藏品信息
4. **填写表单** → 用户确认/修改信息
5. **保存藏品** → 创建藏品记录
6. **上传图片** → 发送到 `/api/collections/upload-image`
7. **保存成功** → 返回图片 URL
### 文件命名示例
**输入**:
- 用户名:`admin`
- 藏品编号:`0001`
- 冠字号:`J051963351`
- 原始文件名:`001.JPG`
**输出**:
- 文件名:`admin-0001-J051963351.JPG`
- 路径:`uploads/collections/admin-0001-J051963351.JPG`
- URL`http://8.149.137.26/uploads/collections/admin-0001-J051963351.JPG`
---
## ⚠️ 注意事项
### 安全限制
1. **文件大小**: 最大 10MB
2. **文件类型**: 仅支持图片image/*
3. **认证要求**: 必须登录才能上传
4. **权限控制**: 只能上传到自己的藏品
### 性能优化
1. **图片压缩**: 建议前端先压缩再上传
2. **CDN 加速**: 生产环境建议使用 CDN
3. **缓存策略**: Nginx 配置静态资源缓存
### 备份策略
1. **定期备份**: 备份 `uploads/collections/` 目录
2. **数据库备份**: 定期导出 `collection_images`
3. **异地备份**: 重要图片建议异地备份
---
## 🔧 故障排查
### 图片不显示
1. 检查文件是否存在:`ls -lh /opt/jiachenlong-backend/uploads/collections/`
2. 检查数据库记录:`SELECT * FROM collection_images;`
3. 检查 Nginx 日志:`tail -f /var/log/nginx/error.log`
4. 检查后端日志:`tail -f /tmp/uvicorn.log`
### 上传失败
1. 检查文件大小是否超限
2. 检查文件类型是否正确
3. 检查藏品 ID 是否存在
4. 检查磁盘空间是否充足
---
**最后更新**: 2026-03-16
**维护人员**: 菜鸟小 D 🤖