417 lines
8.7 KiB
Markdown
417 lines
8.7 KiB
Markdown
# 图片处理流程文档
|
||
|
||
**版本**: 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 🤖
|