Files
Incidents-Data-Portal/backend/app/blueprints/accident/routes.py
T

606 lines
22 KiB
Python
Raw Normal View History

2026-07-10 18:55:55 +08:00
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime, timedelta, timezone
from operator import or_
import traceback
from flask import Blueprint, json, jsonify, current_app, request
from sqlalchemy.exc import SQLAlchemyError
from app.models import AccidList, DictItem, DictType
from app import db
accident_bp = Blueprint("accident_bp", __name__)
@accident_bp.route("/item-list", methods=["POST"])
def get_item_list():
"""
获取字典项列表接口(带分页)
核心逻辑:
- dict_id和item_value都为空 → 全查所有启用的字典项
- 有dict_id则按字典类型过滤,有item_value则按选项值过滤
参数/返回格式:(不变)
"""
try:
# 1. 接收并解析请求参数
req_data = request.get_json() or {}
# dict_id参数处理
dict_id = req_data.get("dict_id")
try:
dict_id = int(dict_id) if dict_id is not None else None
except (ValueError, TypeError):
dict_id = None
item_value = req_data.get("item_value", "").strip()
# 分页参数校验
try:
page = int(req_data.get("pageNum", 1))
page_size = int(req_data.get("pageSize", 10))
page = 1 if page < 1 else page
page_size = 10 if page_size < 1 else page_size
page_size = 50 if page_size > 50 else page_size
except ValueError:
page = 1
page_size = 10
# 2. 构建查询条件(核心:无过滤则全查启用的字典项)
# 基础查询:仅过滤启用状态(status=1)
base_query = DictItem.query.filter(DictItem.status == 1)
# 条件1:按dict_id过滤(有值才加)
if dict_id is not None:
dict_type = DictType.query.filter_by(id=dict_id, status=1).first()
if dict_type:
base_query = base_query.filter(DictItem.dict_id == dict_type.id)
else:
return jsonify({
"code": 200,
"msg": "success",
"data": {
"list": [],
"pagination": {"page": page, "page_size": page_size, "total": 0, "total_pages": 0}
}
})
# 条件2:按item_value过滤(有值才加)
if item_value:
base_query = base_query.filter(DictItem.item_value == item_value)
# 3. 分页查询(paginate简化版)
pagination_obj = base_query.order_by(DictItem.create_time.desc()).paginate(
page=page, per_page=page_size, error_out=False
)
item_list = pagination_obj.items
total = pagination_obj.total
total_pages = pagination_obj.pages
# 4. 格式化返回
result_list = [item.to_dict() for item in item_list]
pagination = {
"page": page,
"page_size": page_size,
"total": total,
"total_pages": total_pages
}
return jsonify({
"code": 200,
"msg": "success",
"data": {"list": result_list, "pagination": pagination}
})
except Exception as e:
print(f"获取字典项列表失败:{str(e)}")
return jsonify({
"code": 500,
"msg": "服务器内部错误",
"data": {
"list": [],
"pagination": {"page": 1, "page_size": 10, "total": 0, "total_pages": 0}
}
})
@accident_bp.route("/all_dict_code", methods=["GET"])
def get_all_dict_code():
"""
获取所有字典类型及对应字典项接口(无参数,返回全量数据)
请求方式:GET
返回格式:
{
"code": 200, // 200成功/500服务器错误
"msg": "success", // 提示信息
"data": [ // 所有字典类型+对应字典项列表
{
"id": 1, // 字典类型ID
"dict_code": "carline", // 字典编码
"status": 1, // 字典类型状态
"items": [ // 该字典编码对应的所有字典项
{"id": 101, "item_value": "大众帕萨特"},
{"id": 102, "item_value": "丰田凯美瑞"}
]
},
{
"id": 2,
"dict_code": "labels",
"status": 1,
"items": [
{"id": 201, "item_value": "碰撞",label:1,value:"123"},
# {"id": 202, "item_value": "异响"}
]
}
]
}
"""
try:
# 1. 查询所有字典类型(按id升序)
dict_type_list = DictType.query.order_by(DictType.id.asc()).all()
# 2. 格式化数据:关联查询每个字典类型对应的字典项
result_data = []
for dict_type in dict_type_list:
# 2.1 查询当前字典类型对应的所有字典项(过滤启用状态,按id升序)
dict_items = DictItem.query.filter(
DictItem.dict_id == dict_type.id, # 关联dict_type的id
DictItem.status == 1 # 仅返回启用的字典项(前端下拉常用)
).order_by(DictItem.id.asc()).all()
# 2.2 格式化字典项(仅保留id和item_value
item_list = [
{
"id": item.id,
"item_value": item.item_value,
"label":item.item_value,
"value":item.item_value,
}
for item in dict_items
]
# 2.3 组装当前字典类型的完整数据
result_data.append({
"id": dict_type.id,
"dict_code": dict_type.dict_code,
"status": dict_type.status,
"items": item_list # 新增:对应字典项的id和item_value
})
# 3. 返回标准化响应
return jsonify({
"code": 200,
"msg": "success",
"data": result_data
})
except Exception as e:
# 异常日志记录(生产环境替换为logging)
print(f"获取所有字典编码及对应项失败:{str(e)}")
# 异常时返回空数组,保证前端格式统一
return jsonify({
"code": 500,
"msg": "服务器内部错误",
"data": []
})
@accident_bp.route("/insert-dict", methods=["POST"])
def insert_dict_type():
"""
新增/恢复字典项接口(新增逻辑优化:存在则恢复status=1,不存在则新增)
请求参数(JSON格式):
{
"dict_id": 1, // 必传,字典类型ID(整数)
"item_value": "大众迈腾" // 必传,字典项值(非空字符串)
}
返回格式:
{
"code": 200, // 200成功/400参数错误/500服务器错误
"msg": "新增成功", // 提示信息(区分新增/更新)
"data": {} // 成功返回数据ID,失败返回空
}
"""
try:
# 1. 接收并解析JSON请求参数
req_data = request.get_json()
if not req_data:
return jsonify({
"code": 400,
"msg": "请求参数不能为空,且必须为JSON格式",
"data": {}
})
# 2. 提取参数并做基础校验
# 2.1 提取dict_id并校验(必传+整数)
dict_id = req_data.get("dict_id")
if dict_id is None:
return jsonify({
"code": 400,
"msg": "dict_id为必传参数",
"data": {}
})
try:
dict_id = int(dict_id)
except (ValueError, TypeError):
return jsonify({
"code": 400,
"msg": "dict_id必须为整数",
"data": {}
})
# 2.2 提取item_value并校验(必传+非空)
item_value = req_data.get("item_value", "").strip()
if not item_value:
return jsonify({
"code": 400,
"msg": "item_value为必传参数,且不能为空",
"data": {}
})
# 3. 业务校验:校验dict_id对应的字典类型是否存在
dict_type = DictType.query.filter_by(id=dict_id).first()
if not dict_type:
return jsonify({
"code": 400,
"msg": f"字典类型ID {dict_id} 不存在",
"data": {}
})
# ========== 核心逻辑修改:先查询所有status的记录 ==========
# 4. 校验dict_id + item_value是否存在(不限制status
exist_item = DictItem.query.filter_by(
dict_id=dict_id,
item_value=item_value
).first()
if exist_item:
# 4.1 记录存在:判断status是否为1
if exist_item.status != 1:
# 非1则改为1(恢复启用)
exist_item.status = 1
exist_item.update_time = datetime.now() # 手动更新时间(若模型未自动配置)
db.session.commit()
# 返回更新成功响应
return jsonify({
"code": 200,
"msg": f"字典项{item_value}已存在,已恢复启用状态(status=1)",
"data": {
"id": exist_item.id,
"dict_id": exist_item.dict_id,
"item_value": exist_item.item_value,
"status": exist_item.status
}
})
else:
# status=1:提示重复,不新增/更新
return jsonify({
"code": 400,
"msg": f"字典类型 {dict_type.dict_code} 下已存在{item_value}(启用状态),请勿重复添加",
"data": {}
})
else:
# 4.2 记录不存在:正常新增
new_dict_item = DictItem(
dict_id=dict_id,
item_value=item_value,
item_label=item_value, # 默认为item_value,如需自定义可改为req_data.get("item_label", item_value)
status=1, # 默认启用
sort=0, # 默认排序值,如需自定义可改为req_data.get("sort", 0)
remark="", # 默认空备注,如需自定义可改为req_data.get("remark", "")
create_time=datetime.now(), # 若模型已设置自动生成,可省略
update_time=datetime.now() # 若模型已设置自动生成,可省略
)
# 插入数据库并提交
db.session.add(new_dict_item)
db.session.commit()
# 返回新增成功响应
return jsonify({
"code": 200,
"msg": "新增字典项成功",
"data": {
"id": new_dict_item.id,
"dict_id": new_dict_item.dict_id,
"item_value": new_dict_item.item_value
}
})
except Exception as e:
# 异常时回滚数据库,避免脏数据
db.session.rollback()
print(f"新增/恢复字典项失败:{str(e)}", exc_info=True)
return jsonify({
"code": 500,
"msg": "服务器内部错误,操作失败",
"data": {}
})
@accident_bp.route("/update-item", methods=["POST"])
def update_item():
"""
根据ID和传入的status,翻转字典项的状态(1→0,0→1)
请求参数:JSON格式 {"id": 10, "status": 1} 或 {"id": 10, "status": 0}
返回格式:标准化JSON响应
"""
try:
# 1. 获取并解析请求的JSON参数
req_data = request.get_json()
if not req_data:
return jsonify({
"code": 400,
"msg": "请求参数不能为空,且必须为JSON格式",
"data": None
}), 400
# 2. 提取并校验id参数
item_id = req_data.get("id")
req_status = req_data.get("status")
# 3. 查询对应的字典项记录
dict_item = DictItem.query.filter_by(id=item_id).first()
if not dict_item:
return jsonify({
"code": 404,
"msg": f"未找到ID为{item_id}的字典项记录",
"data": None
}), 404
# ========== 核心逻辑修改:状态翻转 ==========
# 传入1则改为0,传入0则改为1
target_status = 1 - req_status
dict_item.status = target_status # 更新为目标状态
db.session.commit() # 提交数据库修改
# 5. 返回成功响应(包含更新后的记录信息)
return jsonify({
"code": 200,
"msg": f"字典项状态更新成功(传入{req_status},更新为{target_status}",
"data": dict_item.to_dict()
}), 200
# 捕获JSON解析异常
except ValueError as e:
return jsonify({
"code": 400,
"msg": f"JSON参数解析失败:{str(e)}",
"data": None
}), 400
# 捕获数据库操作异常
except SQLAlchemyError as e:
db.session.rollback()
return jsonify({
"code": 500,
"msg": f"数据库操作失败:{str(e)}",
"data": None
}), 500
# 捕获其他未知异常
except Exception as e:
return jsonify({
"code": 500,
"msg": f"接口执行异常:{str(e)}",
"data": None
}), 500
@accident_bp.route("/insert-accident", methods=["POST"])
def insert_accident():
"""
新增事故工单接口
请求参数:JSON格式(见前端传参示例)
返回格式:标准化JSON响应
"""
try:
# 1. 解析前端JSON参数(无参数/非JSON直接返回错误)
req_data = request.get_json()
# print(111,req_data)
if not req_data:
return jsonify({
"code": 400,
"msg": "请求参数不能为空,且必须为JSON格式",
"data": {}
})
# 2. 必传参数校验(模型中nullable=False的字段)
required_fields = [
"vin", "occur_datetime",
"case_description", "fo_solution"
]
missing_fields = [f for f in required_fields if f not in req_data or not req_data[f]]
if missing_fields:
return jsonify({
"code": 400,
"msg": f"缺少必传参数:{','.join(missing_fields)}",
"data": {}
})
# 3. 时间字段转换(前端传ISO格式字符串 → datetime对象)
# 处理问题发生时间(occur_datetime
try:
# 兼容前端传的带Z的ISO格式(如2026-01-29T06:32:10.017Z
occur_datetime = datetime.fromisoformat(req_data["occur_datetime"].replace("Z", "+00:00"))
except Exception as e:
return jsonify({
"code": 400,
"msg": f"时间格式错误(occur_datetime):请传入ISO格式(如2026-01-29T06:32:10.017Z),错误详情:{str(e)}",
"data": {}
})
# 处理工单新建时间(create_time):前端传则用前端值,否则用当前时间
if "create_time" in req_data and req_data["create_time"]:
try:
create_time = datetime.fromisoformat(req_data["create_time"].replace("Z", "+00:00"))
except Exception as e:
return jsonify({
"code": 400,
"msg": f"时间格式错误(create_time):请传入ISO格式(如2026-01-29T06:32:10.017Z),错误详情:{str(e)}",
"data": {}
})
else:
create_time = datetime.now() # 前端未传则用当前时间
# 4. 构建AccidList实例(严格映射模型字段)
new_accident = AccidList(
ticket_id='',
vin=req_data["vin"].strip(),
occur_datetime=occur_datetime,
case_description=req_data["case_description"].strip(),
fo_solution=req_data["fo_solution"].strip(),
create_time=create_time, # 工单新建时间(前端传则用,否则当前时间)
system_function=req_data['system_function'].strip(),
# 可选字段(前端传则用,否则用模型默认值)
creator=req_data.get("creator", "").strip(), # 创建人(有则更新,无则默认空)
model='', # 型号
carline=req_data.get("carline"), # 车系
labels=req_data.get("labels"), # 事故标签
accident_level=req_data.get("accident_level", "P1"), # 事故评级(默认P1
bmbs_name='', # BMBS联系人
rdca_reporter=req_data.get("rdca_reporter", "").strip(), # RDCA报告人
software_version=req_data.get("software_version", "").strip(), # 软件版本
attachment_url=req_data.get("attachment_url", "").strip(), # 附件地址
)
# 5. 插入数据库并提交
db.session.add(new_accident)
db.session.commit()
# 6. 返回成功响应(包含新增数据的完整信息)
return jsonify({
"code": 200,
"msg": "新增成功",
"data": new_accident.to_dict() # 调用模型的to_dict方法返回结构化数据
})
except Exception as e:
# 异常时回滚数据库,避免脏数据
db.session.rollback()
# 记录详细错误日志(便于排查)
print(f"新增事故工单失败:{str(e)}")
# 返回友好错误提示(不暴露敏感信息)
return jsonify({
"code": 500,
"msg": f"服务器内部错误,新增失败:{str(e)}",
"data": {}
})
@accident_bp.route("/get-accident-record", methods=["POST"])
def get_accident_record():
"""
查询事故工单记录接口(改用in实现多值过滤,无or_)
入参:JSON格式,支持case_description模糊查询、carline/labels/affects_versions多值精确过滤
出参:按create_time从近到远排序的事故记录列表
"""
try:
# 1. 解析前端参数
params = request.get_json(silent=True) or {}
case_desc = params.get("case_description", "").strip()
# 提取并清洗多值参数(转列表+去空)
carline_list = [item.strip() for item in (params.get("carline", [])) if item.strip()]
labels_list = [item.strip() for item in (params.get("labels", [])) if item.strip()]
affects_versions_list = [item.strip() for item in (params.get("affects_versions", [])) if item.strip()]
# 2. 初始化查询对象
query = AccidList.query
# 3. 构建查询条件
# 3.1 case_description:保留模糊查询
if case_desc:
query = query.filter(AccidList.case_description.like(f"%{case_desc}%"))
# 3.2 carline:改用in实现多值精确过滤(核心修改,无or_)
if carline_list:
query = query.filter(AccidList.carline.in_(carline_list))
# 3.3 labels:同carline逻辑
if labels_list:
query = query.filter(AccidList.labels.in_(labels_list))
# 3.4 affects_versions:同carline逻辑
if affects_versions_list:
query = query.filter(AccidList.affects_versions.in_(affects_versions_list))
# 4. 排序+查询
query = query.order_by(AccidList.create_time.desc())
accident_records = query.all()
result = [record.to_dict() for record in accident_records]
# 5. 返回响应
return jsonify({
"code": 200,
"msg": "查询成功",
"data": result
}), 200
except Exception as e:
print(f"查询事故记录异常:{str(e)}\n{traceback.format_exc()}")
return jsonify({
"code": 500,
"msg": f"查询失败:{str(e)}",
"data": None
}), 500
@accident_bp.route("/get-accident-by-id", methods=["POST"])
def get_accident_by_id():
"""根据ID查询事故工单详情接口"""
try:
params = request.get_json(silent=True) or {}
id_str=params.get('id')
if not id_str:
return jsonify({
"code": 400, # 400表示参数错误
"msg": "参数缺失:请传入id参数",
"data": None
}), 400
# 转换为整数,处理非数字的情况
try:
accident_id = int(id_str)
except ValueError:
return jsonify({
"code": 400,
"msg": "参数错误:id必须是整数",
"data": None
}), 400
# 2. 查询数据库
# filter_by按主键id查询,first()获取单条记录(None表示无匹配)
accident = AccidList.query.filter_by(id=accident_id).first()
# 3. 处理查询结果
if not accident:
return jsonify({
"code": 404, # 404表示资源不存在
"msg": f"未找到ID为{accident_id}的事故记录",
"data": None
}), 404
# 4. 格式化并返回数据(使用模型的to_dict()方法)
return jsonify({
"code": 200, # 200表示成功
"msg": "查询成功",
"data": accident.to_dict() # 调用模型自带的转字典方法
}), 200
# 捕获数据库相关异常
except SQLAlchemyError as e:
# 打印异常日志(方便排查问题)
print(f"数据库查询异常:{str(e)}")
return jsonify({
"code": 500, # 500表示服务器内部错误
"msg": "服务器内部错误:数据库查询失败",
"data": None
}), 500
# 捕获其他未知异常
except Exception as e:
print(f"接口异常:{str(e)}")
return jsonify({
"code": 500,
"msg": f"服务器内部错误:{str(e)}",
"data": None
}), 500