from datetime import datetime, timedelta, timezone from queue import Queue import threading from flask import Blueprint, json, jsonify, current_app, request from app.blueprints.incident_gen6.service import aggregate_and_sort_incidents_gen6, aggregate_line_list_gen6, convert_to_histro_data_v2, get_logging_list_gen6, process_incident_data_gen6 from app.link_wedata_utils import sync_query_tencent_cloud_data from app.utils import decrypt_code incident_gen6_bp = Blueprint("incident_gen6_bp", __name__) @incident_gen6_bp.route("/list1", methods=["POST"]) def acc_list1(): encrypted = request.headers.get('X-Encrypted-Timestamp') if not encrypted: return jsonify({"msg": "Parameters are Incorrect"}), 400 encrypt_str=decrypt_code(encrypted) if not encrypt_str: return jsonify({"msg": "Invalid request"}), 400 data = request.get_json() incident_type = data.get("incident_type", "").strip() or None singal_name = data.get("singal_name", []) or None # print("singal_name",singal_name) import os current_dir = os.path.dirname(os.path.abspath(__file__)) json_path = os.path.join(current_dir, "incident_sample.json") with open(json_path, "r", encoding="utf-8") as f: events_data = json.load(f) histo_path = os.path.join(current_dir, "histro.json") with open(histo_path, "r", encoding="utf-8") as f_histro: histo_data = json.load(f_histro) histo_data_new = [] for item in histo_data: new_item = { 'signal': item['signal'], 'time': item['time'], 'value': item['value'], 'valueExplanation': item['valueExplanation'], 'pre_value':item['pre_value'], 'pre_valueExplanation':item['pre_valueExplanation'], 'is_pre_value_needed':item['is_pre_value_needed'] } histo_data_new.append(new_item) histo_list=convert_to_histro_data_v2(histo_data_new) events_data_new=[] for item in events_data: new_item = { 'oneid': item['oneid'], 'incident_name': item['incident_name'], 'incident_time': item['incident_time'], 'key': item['key'], 'signal_value': item['signal_value'], 'latitude': item['latitude'], 'longitude': item['longitude'], 'odometer': item['odometer'], 'speed': item['speed'] } events_data_new.append(new_item) if incident_type == "Parking": # 过滤出 incident_name 等于 Test_I_Park_Trip 的数据 events_data_new = [ item for item in events_data_new if item.get("incident_name") in ["Test_I_Park_Trip", "Test_I_RMA"] ] elif incident_type == "Driving": # 过滤出 incident_name 不等于 Test_I_Park_Trip 的数据 events_data_new = [ item for item in events_data_new if item.get("incident_name") not in ["Test_I_Park_Trip", "Test_I_RMA"] ] # event数据 events_list=process_incident_data_gen6(events_data_new) # 初始的event list数据,但是没有15分钟处理 map_list = aggregate_and_sort_incidents_gen6(events_data_new) # print(f"共 {len(map_list)} 条:") line_list = aggregate_line_list_gen6(map_list) logging_list=get_logging_list_gen6(map_list) response = { "code": 200, "data": { # "events_data":events_data, "events_list": events_list, "map_list": map_list, "line_list": line_list, "logging_list": logging_list, "histo_list":histo_list }, "msg": "success", } return jsonify(response) @incident_gen6_bp.route("/list", methods=["POST"]) def acc_list(): # 验证请求头的信息,通过才继续 encrypted = request.headers.get('X-Encrypted-Timestamp') if not encrypted: return jsonify({"msg": "Parameters are Incorrect"}), 400 encrypt_str=decrypt_code(encrypted) if not encrypt_str: return jsonify({"msg": "Invalid request"}), 400 data = request.get_json() incident_type = data.get("incident_type", "").strip() or None signal_name = data.get("signal_name", []) if not data: return jsonify({"code": 400, "data": None, "msg": "请填写有效参数"}), 400 required_fields = ["incident_time", "oneid"] for field in required_fields: if field not in data: return ( jsonify({"code": 400, "data": None, "msg": f"缺少必需字段: {field}"}), 400, ) incident_time = data["incident_time"] # 检查是否为非空列表 if not isinstance(incident_time, list) or len(incident_time) != 2: return ( jsonify( { "code": 400, "data": [], "msg": "incident_time必须是包含两个时间的数组", } ), 400, ) oneid = data["oneid"] if not isinstance(oneid, str) or not oneid.strip(): return jsonify({"code": 400, "data": None, "msg": "oneid必须是非空字符串"}), 400 config = { 'region': 'ap-shanghai', # 引擎所在地域 'secret_id': current_app.config['WEDATA_SECRET_ID'], 'secret_key': current_app.config['WEDATA_SECRET_KEY'], 'engine': current_app.config['WEDATA_ENGINE'], # 引擎名称 "database":current_app.config['WEDATA_DATABASE'] } # 生成sql try: incid_sql, incid_params = generate_incidents_sql(incident_time, oneid, signal_name, table_name='gen6_incidents') histo_sql, histo_params = generate_incidents_sql(incident_time, oneid, signal_name, table_name='gen6_histograms') except ValueError as e: return jsonify({"code": 400, "data": None, "msg": f"参数错误:{str(e)}"}), 400 print("incid_sql", incid_sql) print("histo_sql", histo_sql) result_queue = Queue(maxsize=2) # 最多存放2个结果 # ========== 仅修改这里:给原函数加极简异常防护(不新增任何函数) ========== def safe_query(*args): """极简包装:仅捕获异常,保证队列必有结果""" try: # 直接调用原函数,参数完全传透(args就是config, sql, params, queue, task_id) sync_query_tencent_cloud_data(*args) except Exception as e: # except Exception as e: # 异常时手动向队列写错误结果(args[3]是queue,args[4]是task_id) args[3].put({"task_id": args[4], "success": False, "data": None, "error": str(e)}) # try: # error_msg=( # f'数据库连接失败:{str(e)}' # if str(e) # else f'底层系统错误:{type(e).__name__}' # ) # args[3].put({"task_id": args[4], "success": False, "data": None, "error":error_msg}) # except Exception as query_err: # print(f'任务失败,无法写入队列:{query_err},原始错误是:{e}') # 5. 创建并启动两个查询线程 thread1 = threading.Thread( target=safe_query, args=(config, incid_sql, incid_params, result_queue, "incidents") # task_id 标记为 incidents ) thread2 = threading.Thread( target=safe_query, args=(config, histo_sql, histo_params, result_queue, "histo") # task_id 标记为 signals ) # 启动线程 thread1.start() thread2.start() thread1.join() thread2.join() # 从队列中提取结果 results_dict = {} for _ in range(2): task_result = result_queue.get() results_dict[task_result["task_id"]] = task_result # 8. 检查是否有查询失败 has_error = False error_msg = "" for task_id, res in results_dict.items(): if not res["success"]: has_error = True error_msg = f"查询异常:{res['error'][:80]}" # 错误信息保留前80 if has_error: return jsonify({ "code": 500, "msg": f"查询失败:{error_msg}", "data": None }), 500 # 9. 获取两个表的查询结果(业务处理核心) events_data = results_dict["incidents"]["data"] histo_data = results_dict["histo"]["data"] histo_list=convert_to_histro_data_v2(histo_data) if incident_type == "Parking": # 过滤出 incident_name 等于 Test_I_Park_Trip 的数据 events_data = [ item for item in events_data if item.get("incident_name") in ["Test_I_Park_Trip", "Test_I_RMA"] ] elif incident_type == "Driving": # 过滤出 incident_name 不等于 Test_I_Park_Trip 的数据 events_data = [ item for item in events_data if item.get("incident_name") not in ["Test_I_Park_Trip", "Test_I_RMA"] ] # event数据 events_list=process_incident_data_gen6(events_data) # 初始的event list数据,但是没有15分钟处理 map_list = aggregate_and_sort_incidents_gen6(events_data) line_list = aggregate_line_list_gen6(map_list) logging_list=get_logging_list_gen6(map_list) response = { "code": 200, "data": { # "events_data":events_data, "events_list": events_list, "map_list": [], "line_list": line_list, "logging_list": logging_list, "histo_list":histo_list }, "msg": "success", } return jsonify(response) def generate_incidents_sql(incident_time: list, oneid: str, signal_list: list, table_name: str) -> tuple: """ 简化版:仅处理用到的两个表,字段名映射极简,去掉冗余扩展 - gen6_incidents:时间=incident_time,ID字段=oneid - gen6_histograms:时间=time,ID字段=vin """ # 处理UTC时间(带T/Z)转本地时间(东八区) def convert_utc_to_cst(utc_time_str): try: # 解析UTC时间(支持带T/Z的格式) utc_dt = datetime.fromisoformat(utc_time_str.replace('Z', '+00:00')) # 转东八区(UTC+8) cst_tz = timezone(timedelta(hours=8)) cst_dt = utc_dt.astimezone(cst_tz) # 转为数据库兼容的格式(YYYY-MM-DD HH:mm:ss.fff) return cst_dt.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] # 保留3位毫秒 except Exception as e: # 解析失败则返回原时间(兼容测试参数) return utc_time_str # 转换时间范围 incident_time = [convert_utc_to_cst(t) for t in incident_time] # 1. 极简字段映射(只保留用到的表,避免KeyError) FIELD_MAP = { "gen6_incidents": {"time": "incident_time", "id": "oneid", # "select_fields": "oneid, incident_name,incident_time,`key`,signal_value,latitude,longitude,odometer,speed" }, "select_fields": "*" }, "gen6_histograms": {"time": "time", "id": "vin", "select_fields": "signal, time, value, valueExplanation"}, } # 2. 基础校验(只校验用到的表,去掉冗余) if table_name not in FIELD_MAP: raise ValueError(f"仅支持查询表:{list(FIELD_MAP.keys())}") if len(incident_time) != 2 or not all(incident_time): raise ValueError("incident_time必须是包含两个非空时间的列表") if not isinstance(oneid, str) or not oneid.strip(): raise ValueError("oneid必须是非空字符串") # 3. 提取当前表的字段名(核心:只用两行完成字段替换) time_field = FIELD_MAP[table_name]["time"] id_field = FIELD_MAP[table_name]["id"] select_fields = FIELD_MAP[table_name]["select_fields"] # 4. 拼接SQL条件(极简逻辑,去掉冗余注释) conditions = [ f"{time_field} BETWEEN %s AND %s", f"{id_field} = %s" ] params = [incident_time[0], incident_time[1], oneid] # 仅gen6_histograms处理signal条件 if table_name == "gen6_histograms": if signal_list: signal_placeholders = ",".join(["%s"] * len(signal_list)) conditions.append(f"signal IN ({signal_placeholders})") params += signal_list else: conditions.append("signal = %s") params.append("DAS_DTR_UI_Stat_ST3") # 5. 生成最终SQL(简化格式化) sql = f"SELECT {select_fields} FROM {table_name} WHERE {' AND '.join(conditions)}" return sql, params