262 lines
9.0 KiB
Python
262 lines
9.0 KiB
Python
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||
from datetime import datetime, timedelta, timezone
|
||
from operator import or_
|
||
import os
|
||
import random
|
||
import string
|
||
import traceback
|
||
from flask import Blueprint, json, jsonify, current_app, request
|
||
from sqlalchemy.exc import SQLAlchemyError
|
||
from app.models import AccidList, DictItem, DictType, FileUpload
|
||
from app import db
|
||
|
||
file_uploads_bp = Blueprint("file_uploads_bp", __name__)
|
||
|
||
ALLOWED_EXTENSIONS={'png', 'jpg', 'jpeg', 'gif'}
|
||
|
||
|
||
|
||
def allowed_file(filename):
|
||
"""验证图片格式是否合法"""
|
||
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||
|
||
|
||
def generate_file_alias(filename):
|
||
"""生成唯一存储别名(避免重名):时间戳+6位随机字符串+扩展名"""
|
||
ext = filename.rsplit('.', 1)[1].lower()
|
||
random_str = ''.join(random.choices(string.ascii_letters + string.digits, k=6))
|
||
timestamp = int(datetime.now().timestamp())
|
||
return f"{timestamp}_{random_str}.{ext}"
|
||
|
||
|
||
# 接口1:图片上传(基础接口,返回文件ID)
|
||
@file_uploads_bp.route('/upload-image', methods=['POST'])
|
||
def upload_image():
|
||
"""
|
||
图片上传接口
|
||
- 请求方式:POST
|
||
- Content-Type: multipart/form-data
|
||
- 请求参数:
|
||
file: 图片文件(必传)
|
||
create_by: 上传人(可选,如admin)
|
||
- 返回:文件ID、访问URL等信息
|
||
"""
|
||
try:
|
||
# 1. 校验文件是否存在
|
||
if 'file' not in request.files:
|
||
return jsonify({'code': 400, 'msg': '请选择要上传的图片', 'data': None}), 400
|
||
file = request.files['file']
|
||
if file.filename == '':
|
||
return jsonify({'code': 400, 'msg': '文件名不能为空', 'data': None}), 400
|
||
|
||
# 2. 校验文件格式
|
||
if not allowed_file(file.filename):
|
||
return jsonify({
|
||
'code': 400,
|
||
'msg': f'仅支持{"/".join(ALLOWED_EXTENSIONS)}格式的图片',
|
||
'data': None
|
||
}), 400
|
||
|
||
# 3. 创建上传目录(不存在则自动创建)
|
||
os.makedirs(current_app.config['UPLOAD_FOLDER'], exist_ok=True)
|
||
|
||
# 4. 生成唯一别名并保存文件
|
||
file_alias = generate_file_alias(file.filename)
|
||
file_save_path = os.path.join(current_app.config['UPLOAD_FOLDER'], file_alias)
|
||
file.save(file_save_path)
|
||
|
||
# 5. 写入FileUpload表
|
||
file_size = os.path.getsize(file_save_path) # 获取文件大小(字节)
|
||
file_type = file.content_type # 获取MIME类型(如image/jpeg)
|
||
create_by = request.form.get('create_by', None)
|
||
|
||
new_file = FileUpload(
|
||
file_name=file.filename,
|
||
file_alias=file_alias,
|
||
file_path=f"/uploads/{file_alias}", # 相对路径,前端拼接域名访问
|
||
file_size=file_size,
|
||
file_type=file_type,
|
||
create_by=create_by,
|
||
business_type='case', # 固定关联事故工单业务
|
||
business_id=None # 暂不关联具体工单,后续绑定
|
||
)
|
||
db.session.add(new_file)
|
||
db.session.commit()
|
||
|
||
# 6. 返回成功响应
|
||
return jsonify({
|
||
'code': 200,
|
||
'msg': '图片上传成功',
|
||
'data': new_file.to_dict()
|
||
}), 200
|
||
|
||
except Exception as e:
|
||
db.session.rollback() # 异常回滚
|
||
return jsonify({
|
||
'code': 500,
|
||
'msg': f'上传失败:{str(e)}',
|
||
'data': None
|
||
}), 500
|
||
|
||
|
||
# 接口2:绑定图片到事故工单
|
||
@file_uploads_bp.route('/bind-image', methods=['POST'])
|
||
def bind_image_to_case():
|
||
"""
|
||
绑定图片到事故工单(更新image_ids和business_id)
|
||
- 请求方式:POST
|
||
- Content-Type: application/json
|
||
- 请求参数:
|
||
case_id: 事故工单ID(必传)
|
||
file_ids: 图片ID列表(如[1,2,3],必传)
|
||
"""
|
||
try:
|
||
data = request.get_json()
|
||
case_id = data.get('case_id')
|
||
file_ids = data.get('file_ids', [])
|
||
|
||
# 1. 校验参数
|
||
if not case_id or not isinstance(file_ids, list) or len(file_ids) == 0:
|
||
return jsonify({'code': 400, 'msg': '工单ID和图片ID列表不能为空', 'data': None}), 400
|
||
|
||
# 2. 检查工单是否存在
|
||
case = AccidList.query.get(case_id)
|
||
if not case:
|
||
return jsonify({'code': 404, 'msg': '事故工单不存在', 'data': None}), 404
|
||
|
||
# 3. 拼接图片ID串(去重)
|
||
existing_ids = case.image_ids.split(',') if case.image_ids.strip() else []
|
||
new_ids = list(set(existing_ids + [str(fid) for fid in file_ids])) # 去重
|
||
case.image_ids = ','.join(new_ids)
|
||
case.update_at = db.func.current_timestamp() # 更新工单修改时间
|
||
|
||
# 4. 更新FileUpload的business_id(关联具体工单)
|
||
FileUpload.query.filter(
|
||
FileUpload.id.in_(file_ids),
|
||
FileUpload.business_type == 'case'
|
||
).update({'business_id': case_id}, synchronize_session=False)
|
||
|
||
db.session.commit()
|
||
|
||
# 5. 返回结果
|
||
return jsonify({
|
||
'code': 200,
|
||
'msg': '图片绑定成功',
|
||
'data': {
|
||
'case_id': case_id,
|
||
'image_ids': case.image_ids,
|
||
'image_count': len(new_ids)
|
||
}
|
||
}), 200
|
||
|
||
except Exception as e:
|
||
db.session.rollback()
|
||
return jsonify({
|
||
'code': 500,
|
||
'msg': f'绑定失败:{str(e)}',
|
||
'data': None
|
||
}), 500
|
||
|
||
|
||
# 接口3:查询事故工单关联的图片
|
||
@file_uploads_bp.route('/get-images/<int:case_id>', methods=['GET'])
|
||
def get_case_images(case_id):
|
||
"""
|
||
查询工单关联的所有有效图片
|
||
- 请求方式:GET
|
||
- 路径参数:case_id - 事故工单ID
|
||
"""
|
||
try:
|
||
# 1. 检查工单是否存在
|
||
case = AccidList.query.get(case_id)
|
||
if not case:
|
||
return jsonify({'code': 404, 'msg': '事故工单不存在', 'data': None}), 404
|
||
|
||
# 2. 拆分图片ID并查询
|
||
image_ids = case.image_ids.split(',') if case.image_ids.strip() else []
|
||
if not image_ids:
|
||
return jsonify({'code': 200, 'msg': '暂无关联图片', 'data': []}), 200
|
||
|
||
# 3. 查询有效图片(status=1)
|
||
images = FileUpload.query.filter(
|
||
FileUpload.id.in_(image_ids),
|
||
FileUpload.status == 1
|
||
).all()
|
||
|
||
# ========== 核心修改:拼接完整图片URL ==========
|
||
image_base_url = current_app.config['IMAGE_BASE_URL'] # 获取配置的基础URL
|
||
image_data = []
|
||
for img in images:
|
||
img_dict = img.to_dict() # 原有模型的to_dict方法
|
||
# 把相对路径拼接成完整URL(覆盖原有file_path)
|
||
img_dict['file_path'] = f"{image_base_url}{img_dict['file_path']}"
|
||
image_data.append(img_dict)
|
||
|
||
# 4. 返回结果(改用拼接后的image_data)
|
||
return jsonify({
|
||
'code': 200,
|
||
'msg': '查询成功',
|
||
'data': image_data # 现在data里的file_path是完整URL
|
||
}), 200
|
||
|
||
except Exception as e:
|
||
return jsonify({
|
||
'code': 500,
|
||
'msg': f'查询失败:{str(e)}',
|
||
'data': None
|
||
}), 500
|
||
|
||
|
||
# 接口4:移除工单关联的图片(逻辑删除)
|
||
@file_uploads_bp.route('/remove-image', methods=['POST'])
|
||
def remove_case_image():
|
||
"""
|
||
移除工单关联的图片(逻辑删除)
|
||
- 请求方式:POST
|
||
- Content-Type: application/json
|
||
- 请求参数:
|
||
case_id: 工单ID(必传)
|
||
file_id: 图片ID(必传)
|
||
"""
|
||
try:
|
||
data = request.get_json()
|
||
case_id = data.get('case_id')
|
||
file_id = data.get('file_id')
|
||
|
||
# 1. 校验参数
|
||
if not case_id or not file_id:
|
||
return jsonify({'code': 400, 'msg': '工单ID和图片ID不能为空', 'data': None}), 400
|
||
|
||
# 2. 检查工单和图片是否存在
|
||
case = AccidList.query.get(case_id)
|
||
file = FileUpload.query.get(file_id)
|
||
if not case:
|
||
return jsonify({'code': 404, 'msg': '事故工单不存在', 'data': None}), 404
|
||
if not file:
|
||
return jsonify({'code': 404, 'msg': '图片不存在', 'data': None}), 404
|
||
|
||
# 3. 移除工单的image_ids中的该ID
|
||
existing_ids = case.image_ids.split(',') if case.image_ids.strip() else []
|
||
if str(file_id) in existing_ids:
|
||
existing_ids.remove(str(file_id))
|
||
case.image_ids = ','.join(existing_ids)
|
||
case.update_at = db.func.current_timestamp()
|
||
|
||
# 4. 逻辑删除图片(status=0)
|
||
file.status = 0
|
||
db.session.commit()
|
||
|
||
return jsonify({
|
||
'code': 200,
|
||
'msg': '图片移除成功',
|
||
'data': {'case_id': case_id, 'file_id': file_id}
|
||
}), 200
|
||
|
||
except Exception as e:
|
||
db.session.rollback()
|
||
return jsonify({
|
||
'code': 500,
|
||
'msg': f'移除失败:{str(e)}',
|
||
'data': None
|
||
}), 500
|