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