333 lines
13 KiB
Python
333 lines
13 KiB
Python
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 |