init
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
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
|
||||
Reference in New Issue
Block a user