from datetime import datetime import re from flask import Blueprint, current_app, json, request, jsonify from flask_jwt_extended import jwt_required import requests from sqlalchemy import and_, func from app import db from app.models import BagFile, Fst from app.utils.fst_tree import build_tree remote_bp = Blueprint("remote", __name__) def extract_datetime_from_filename(bag_filename): """ 从文件名中提取日期和时间。 返回包含年月日时分秒的字符串、datetime对象以及视频URL。 """ match = re.search(r"_(\d{8})-(\d{6})_", bag_filename) if not match: raise ValueError("文件名格式错误,无法提取时间信息") date_part, time_part = match.groups() # 提取年月日时分秒各部分 year = date_part[:4] month = date_part[4:6] day = date_part[6:8] hour = time_part[:2] minute = time_part[2:4] second = time_part[4:6] # 年月日时分秒整合到一个变量(字符串格式) datetime_full = f"{year}-{month}-{day} {hour}:{minute}:{second}" # 原有视频路径相关逻辑保持不变 bag_filename = bag_filename.replace(".bag", "") video_final_path = f"{bag_filename}- Wide.mp4" video_cos = f"momenta/videos/{year}/{month}/{day}/{video_final_path}" video_url = ( "https://data-miningc01-1318950322.cos.ap-shanghai-adc.myqcloud.com/" + video_cos ) # 返回整合后的完整日期时间字符串、datetime对象和视频URL return datetime_full, video_url def get_level1_labels(root_data, target_level1_id, use_label=False): """ 收集特定一级标签及其所有子标签的id(或label)为扁平数组 :param root_data: 原始数据(字典或JSON字符串) :param target_level1_id: 目标一级标签的id :param use_label: 若为True则返回label,否则返回id(默认False) :return: 扁平标签数组(如未找到目标则返回空数组) """ # 确保root_data是字典 if isinstance(root_data, str): try: root_data = json.loads(root_data) except json.JSONDecodeError: raise ValueError("root_data无法解析为JSON") if not isinstance(root_data, dict): raise TypeError("root_data必须是字典或JSON字符串") result = [] key = "label" if use_label else "id" # 选择使用label还是id def recursive_collect(node): """递归收集节点的标签(id或label)""" if not isinstance(node, dict) or key not in node: return # 只添加当前节点的标签(不保留其他字段) result.append(node[key]) # 递归处理子节点 for child in node.get("children", []): recursive_collect(child) # 查找目标一级标签 target_node = None for node in root_data.get("children", []): if ( isinstance(node, dict) and node.get("level") == 1 and node.get("id") == target_level1_id ): target_node = node break # 收集目标节点及其所有子标签 if target_node: recursive_collect(target_node) return result @remote_bp.route("/fstmenu", methods=["GET"]) def fstmenu(): # pass """ 返回值: { path: string name: string title: string component: string children?: BackendRoute[] } """ level1_tags = Fst.query.filter(Fst.level == 1).all() # result = [ # {"id": tag.id, "name": tag.name, "level": tag.level} for tag in level1_tags # ] result = [ { "path": tag.name, "name": tag.name, "title": tag.name, "component": "", } for tag in level1_tags ] return jsonify(result) @remote_bp.route("/fstmenu1", methods=["GET"]) def fstmenu1(): # pass """ 返回值: { path: string name: string title: string component: string children?: BackendRoute[] } """ API_URL = "http://10.0.240.4:5232/api/fst/print_tree" TIMEOUT = 10 # 超时时间(秒) try: response = requests.get(url=API_URL, timeout=TIMEOUT) # 3. 验证HTTP响应状态 response.raise_for_status() level1_results = [] # 遍历最外层的 children 列表(level=0 的子节点即 level=1) for node in response.json()[0]["children"]: # 二次验证 level 是否为 1(避免结构异常) if node.get("level") == 1: level1_results.append( { "path": node["id"], "name": node["label"], "title": node["label"], "component": "", } ) for node_park in response.json()[1]["children"]: # 二次验证 level 是否为 1(避免结构异常) if node_park.get("level") == 1: level1_results.append( { "path": node_park["id"], "name": node_park["label"], "title": node_park["label"], "component": "", } ) return jsonify(level1_results) except Exception as e: # 其他未捕获异常 return jsonify({"success": False, "message": f"服务器处理错误:{str(e)}"}) # 根据fst标签返回bag信息----一级标签的所有 @remote_bp.route("/remote-baglist1", methods=["POST"]) def query_bag_file_by_fst(): try: # 1. 解析核心请求参数(仅保留:分页参数 + Fst 1-4级标签) params = request.get_json() or {} print(f"【请求参数】原始参数: {params}") # 分页参数(默认第1页,每页20条) page = params.get("page", 1) per_page = params.get("per_page", 20) # Fst 标签参数(1-4级标签,字符串格式ID) level1_tag = params.get("level1_tag", "").strip() level2_tag = params.get("level2_tag", "").strip() level3_tag = params.get("level3_tag", "").strip() level4_tag = params.get("level4_tag", "").strip() # 2. 构建基础查询 query = BagFile.query # 3. 核心过滤:仅保留 Fst 标签筛选条件 conditions = [] # 一级标签过滤(转换为整数ID匹配) if level1_tag: try: conditions.append(BagFile.level1_tag_id == int(level1_tag)) except ValueError: print(f"【过滤警告】一级标签ID格式错误: {level1_tag}") # 二级标签过滤 if level2_tag: try: conditions.append(BagFile.level2_tag_id == int(level2_tag)) except ValueError: print(f"【过滤警告】二级标签ID格式错误: {level2_tag}") # 三级标签过滤 if level3_tag: try: conditions.append(BagFile.level3_tag_id == int(level3_tag)) except ValueError: print(f"【过滤警告】三级标签ID格式错误: {level3_tag}") # 四级标签过滤 if level4_tag: try: conditions.append(BagFile.level4_tag_id == int(level4_tag)) except ValueError: print(f"【过滤警告】四级标签ID格式错误: {level4_tag}") # 应用标签过滤条件 if conditions: query = query.filter(and_(*conditions)) # 4. 核心排序:按 bag_update_time 降序(最新更新优先) query = query.order_by(BagFile.bag_update_time.desc()) # 5. 分页查询(页码超出时返回空列表,不报错) pagination = query.paginate(page=page, per_page=per_page, error_out=False) total_items = pagination.total # 总数据条数 total_pages = pagination.pages # 总页数 # 6. 数据序列化(仅保留核心字段,包含 Fst 标签信息) bag_files = [] for bag in pagination.items: # 拼接 Fst 标签“中文+英文”名称(提升可读性) level1_name = f"{bag.level1_tag.name}" if bag.level1_tag else None level2_name = f"{bag.level2_tag.name}" if bag.level2_tag else None level3_name = f"{bag.level3_tag.name}" if bag.level3_tag else None level4_name = f"{bag.level4_tag.name}" if bag.level4_tag else None bag_files.append( { "id": bag.id, "file_name": bag.file_name, # BAG文件名 "capture_datetime": ( bag.capture_datetime.isoformat() if bag.capture_datetime else None ), # 采集时间 "bag_update_time": ( bag.bag_update_time.isoformat() if bag.bag_update_time else None ), # 排序依据字段 # Fst 标签核心信息 "level1_tag_id": bag.level1_tag_id, "level1_tag_name": level1_name, "level2_tag_id": bag.level2_tag_id, "level2_tag_name": level2_name, "level3_tag_id": bag.level3_tag_id, "level3_tag_name": level3_name, "level4_tag_id": bag.level4_tag_id, "level4_tag_name": level4_name, # 基础状态字段(按需保留,非核心可删除) "status": bag.status.name if bag.status else None, "bag_status": bag.bag_status, "comment": bag.comment1, "front_starttime": bag.front_start_sec, "front_endtime": bag.front_end_sec, "case_type": "简单场景" if bag.case_type == 1 else "复杂场景", "highway": "高速" if bag.high_speed == 1 else None, "city": "城区" if bag.urban == 1 else None, "driving": "parking" if bag.urban else "driving", "video_url": bag.video_url, } ) # 7. 返回精简响应 return jsonify( { "code": 200, "data": bag_files, "total": total_items, "page": page, "pages": total_pages, "message": "根据 Fst 标签查询成功", } ) except Exception as e: # 异常捕获与响应 error_msg = f"查询出错:{str(e)}" print(f"【接口异常】{error_msg}") return ( jsonify( { "code": 500, "data": [], "total": 0, "page": page if "page" in locals() else 1, "pages": 0, "message": error_msg, } ), 500, ) @remote_bp.route("/remote-baglist", methods=["POST"]) def query_bag_file_by_fst1(): # 1. 解析核心请求参数(仅保留:分页参数 + Fst 1-4级标签) params = request.get_json() or {} print(f"【请求参数】原始参数: {params}") # 分页参数(默认第1页,每页20条) page = params.get("page", 1) per_page = params.get("per_page", 20) # Fst 标签参数(1-4级标签,字符串格式ID) fst_tag_list = params.get("fst_tag", "") print(f"【请求参数】Fst 标签列表: {fst_tag_list}") # 获取过滤的参数 filter_fst = params.get("filter_fst", []) # 把请求到的数据进行处理 API_LEVEL1_URL = "http://10.0.240.4:5232/api/fst/print_tree" API_NODES_URL = "http://10.0.240.4:5232/api/fst/bags/nodes" API_OTHER_INFO_URL = "http://10.0.240.4:5232/api/bags/fst/nodes" TIMEOUT = 10 # 超时时间(秒) try: # 如果filter_fst为空,说明为一级标签 if len(filter_fst) == 0: response = requests.get(url=API_LEVEL1_URL, timeout=TIMEOUT) # print(123,response.json()) # 3. 验证HTTP响应状态 response.raise_for_status() # 遍历最外层的 children 列表(level=0 的子节点即 level=1) driving_tree = response.json()[0] park_tree = response.json()[1] # 获取所有的一级标签的子标签,组成一个新的列表 driving_fst_list = get_level1_labels(driving_tree, fst_tag_list) park_fst_list = get_level1_labels(park_tree, fst_tag_list) if len(driving_fst_list)>0: fst_list=driving_fst_list if len(park_fst_list)>0: fst_list=park_fst_list # print(555,fst_list) filter_fst = fst_list # 查询所有的标签 query_params = {"page": page, "per_page": per_page} # 页码 # 每页条数 # bag_files = [] if filter_fst: res_bags = requests.post( url=API_NODES_URL, params=query_params, # URL查询参数(自动拼接为 ?page=1&per_page=20) json=filter_fst, # JSON请求体(自动设置Content-Type头) timeout=TIMEOUT, # 超时时间(秒) ) # 对查询到结果,循环进行查询 all_bag_list = res_bags.json()["items"] total_nums = res_bags.json()["total"] # 获取所有的bag_name,组成一个列表 bag_name_list = [] for item in all_bag_list: bag_name = item.get("bag_name") if bag_name: bag_name_list.append(bag_name) # 查询所有的bagname的列表数据,返回的数据是字典中对应的列表 bag_other_info_list = requests.post(url=API_OTHER_INFO_URL, json=bag_name_list) # 组装数据 res_list=[] for row in all_bag_list: bag_name = row.get("bag_name") if bag_name: record={} record["file_name"]=bag_name record["level1_tag_name"] = fst_tag_list record["sub_tag_name"] = row.get('sts') # 过滤值 bag_other_detail=bag_other_info_list.json()[bag_name] if len(bag_other_detail)==0: record["comment"] = "" record["highway"] = "" record["city"] = "" record["driving"] = "" record["front_starttime"] = "" record["front_endtime"] = "" if len(bag_other_detail)==1: record["comment"] = bag_other_detail[0]["comments"] record["highway"] = "" record["city"] = "" record["driving"] = "" record["front_starttime"] = bag_other_detail[0]["start"] record["front_endtime"] = bag_other_detail[0]["end"] if len(bag_other_detail)>1: # 循环bag_other_detail的值,当row.get('sts')等于某项的name时。 for info in bag_other_detail: if info['name']==row.get('sts'): record["comment"] = info['comments'] record["highway"] = "" record["city"] = "" record["driving"] = "" record["front_starttime"] = info['start'] record["front_endtime"] = info['end'] else: record["comment"] = "" record["highway"] = "" record["city"] = "" record["driving"] = "" record["front_starttime"] = "" record["front_endtime"] = "" datetime_full, video_url = extract_datetime_from_filename(bag_name) record["video_url"] = video_url record["capture_datetime"] = datetime_full res_list.append(record) # 7. 返回精简响应 return jsonify( { "code": 200, "data": res_list, "total": total_nums, "page": page, "pages": page, "message": "根据 Fst 标签查询成功", } ) else: return ( jsonify( { "code": 500, "data": [], "total": 0, "page": page if "page" in locals() else 1, "pages": 0, "message": "标签数据出错", } ), 500, ) except Exception as e: # 异常捕获与响应 error_msg = f"查询出错:{str(e)}" print(f"【接口异常】{error_msg}") return ( jsonify( { "code": 500, "data": [], "total": 0, "page": page if "page" in locals() else 1, "pages": 0, "message": error_msg, } ), 500, ) @remote_bp.route("/sub-fst", methods=["GET"]) def get_sub_fst1(): target_label = request.args.get("fst_id") API_URL = "http://10.0.240.4:5232/api/fst/print_tree" TIMEOUT = 10 # 超时时间(秒) try: response = requests.get(url=API_URL, timeout=TIMEOUT) # 3. 验证HTTP响应状态 response.raise_for_status() # 遍历最外层的 children 列表(level=0 的子节点即 level=1) result=[] for node in response.json()[0].get("children", []): if node.get("level") == 1 and node.get("label") == target_label: data = node.get("children", []) if data: result=data for node_park in response.json()[1].get("children", []): if node_park.get("level") == 1 and node_park.get("label") == target_label: data_park = node_park.get("children", []) if data_park: result=data_park return jsonify({"code": 200, "data": result}) # 返回该节点的children return ( jsonify({"code": 404, "message": "Fst not found"}), 404, ) # 未找到匹配节点时返回None except Exception as e: return jsonify({"success": False, "message": f"服务器处理错误:{str(e)}"}) @remote_bp.route("/all-fst", methods=["GET"]) def get_all_fst1(): type = request.args.get("type") API_URL = "http://10.0.240.4:5232/api/fst/print_tree" TIMEOUT = 10 # 超时时间(秒) try: response = requests.get(url=API_URL, timeout=TIMEOUT) # 3. 验证HTTP响应状态 response.raise_for_status() if type == "driving": return jsonify({"code": 200, "data": response.json()[0]}) if type == "parking": return jsonify({"code": 200, "data": response.json()[1]}) except Exception as e: return jsonify({"success": False, "message": f"服务器处理错误:{str(e)}"})