提交
This commit is contained in:
0
app/blueprints/__init__.py
Normal file
0
app/blueprints/__init__.py
Normal file
5
app/blueprints/auth/__init__.py
Normal file
5
app/blueprints/auth/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# from flask import Blueprint
|
||||
|
||||
# auth_bp = Blueprint('auth', __name__)
|
||||
|
||||
# from . import routes
|
||||
522
app/blueprints/auth/routes.py
Normal file
522
app/blueprints/auth/routes.py
Normal file
@@ -0,0 +1,522 @@
|
||||
from datetime import datetime
|
||||
import json
|
||||
from flask import Blueprint, current_app, request, jsonify
|
||||
from flask_jwt_extended import create_access_token, get_jwt, get_jwt_identity, jwt_required
|
||||
from sqlalchemy import and_
|
||||
from app import jwt,db
|
||||
from app.models import OperationHistory, Role, User, UserRole
|
||||
from app.utils.password_hasher import hash_password,check_password
|
||||
from app.utils.response_dict import response
|
||||
|
||||
|
||||
auth_bp= Blueprint('data', __name__)
|
||||
|
||||
|
||||
@auth_bp.route('/login', methods=['POST'])
|
||||
def login():
|
||||
# 1. 获取登录凭证
|
||||
data = request.get_json()
|
||||
username = data.get('username')
|
||||
password = data.get('password')
|
||||
print('username',username)
|
||||
print('password',password)
|
||||
|
||||
# 2. 验证必填字段
|
||||
# if not username or not password:
|
||||
# response['code']=400
|
||||
# response['message']="用户名和密码不能为空"
|
||||
# return jsonify(response)
|
||||
#
|
||||
# # 3. 查询用户
|
||||
# user = User.query.filter_by(username=username).first()
|
||||
#
|
||||
# # 4. 验证用户存在性和密码
|
||||
# if not user or not check_password(password,user.password):
|
||||
# response['code']=401
|
||||
# response['message']="用户名或密码错误"
|
||||
# return jsonify({"msg": "用户名或密码错误"}), 401
|
||||
#
|
||||
# if user.status != 1:
|
||||
# return jsonify({"msg": "用户已禁用"}), 403
|
||||
|
||||
# 5. 生成JWT令牌[2,6](@ref)
|
||||
access_token = create_access_token(identity=str(1))
|
||||
|
||||
|
||||
result = db.session.query(
|
||||
User.id,
|
||||
User.username.label('realName'), # 使用 label 实现别名
|
||||
User.password,
|
||||
User.status,
|
||||
Role.name.label('roles'),
|
||||
Role.description
|
||||
).join(
|
||||
UserRole, User.id == UserRole.user_id # 第一次 JOIN:用户 ↔ 用户角色
|
||||
).join(
|
||||
Role, UserRole.role_id == Role.id # 第二次 JOIN:用户角色 ↔ 角色
|
||||
).filter(
|
||||
User.status == 1, # 状态过滤
|
||||
User.id == 1 # 用户ID过滤
|
||||
).all()
|
||||
|
||||
if not result:
|
||||
return jsonify({"msg": "用户未分配角色或已禁用"}), 403
|
||||
|
||||
|
||||
# dict_list = [row._asdict() for row in result]
|
||||
# dict_list[0]['accessToken']=access_token
|
||||
# dict_list[0]['roles']=[dict_list[0]['roles']]
|
||||
# data={
|
||||
# "accessToken": access_token,
|
||||
# "user_id": user.id,
|
||||
# "message": "登录成功"
|
||||
# }
|
||||
|
||||
# response['data']= dict_list[0]
|
||||
# response['error']=''
|
||||
# 6. 返回响应
|
||||
res={"accessToken":access_token}
|
||||
data={}
|
||||
data['data']=res
|
||||
return jsonify(res)
|
||||
|
||||
|
||||
@auth_bp.route('/userinfo', methods=['GET'])
|
||||
@jwt_required() # 要求有效Token[1,5](@ref)
|
||||
def get_user_info():
|
||||
userid = get_jwt_identity()
|
||||
"""
|
||||
获取用户信息
|
||||
"""
|
||||
result = db.session.query(
|
||||
User.id,
|
||||
User.username.label('realName'), # 使用 label 实现别名
|
||||
User.password,
|
||||
User.status,
|
||||
Role.name.label('roles'),
|
||||
Role.description
|
||||
).join(
|
||||
UserRole, User.id == UserRole.user_id # 第一次 JOIN:用户 ↔ 用户角色
|
||||
).join(
|
||||
Role, UserRole.role_id == Role.id # 第二次 JOIN:用户角色 ↔ 角色
|
||||
).filter(
|
||||
User.status == 1, # 状态过滤
|
||||
User.id == userid # 用户ID过滤
|
||||
).all()
|
||||
dict_list = [row._asdict() for row in result]
|
||||
if not dict_list:
|
||||
return jsonify({"msg": "用户未分配角色或已禁用"}), 403
|
||||
role_list = [row.get('roles') for row in dict_list if row.get('roles')]
|
||||
if not role_list:
|
||||
return jsonify({"msg": "用户未分配角色或已禁用"}), 403
|
||||
dict_list[0]['roles'] = role_list
|
||||
|
||||
data={"roles": dict_list[0]['roles'],"realName": dict_list[0]['realName'],"homePath": '/datamanage/datalabel'}
|
||||
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@auth_bp.route('/codes', methods=['GET'])
|
||||
@jwt_required() # 要求有效Token[1,5](@ref)
|
||||
def get_code():
|
||||
response['code']=200
|
||||
response['data']=["AC_100100","AC_100110"]
|
||||
return jsonify(response)
|
||||
|
||||
|
||||
@auth_bp.route('/refresh', methods=['POST'])
|
||||
@jwt_required() # 要求有效Token[1,5](@ref)
|
||||
def refresh():
|
||||
# data: string;
|
||||
# status: number;
|
||||
token = create_access_token(identity='service_account')
|
||||
data={}
|
||||
data['data']=token
|
||||
data['status']=1
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
# ===== 受保护路由示例 =====
|
||||
@auth_bp.route('/protected', methods=['GET'])
|
||||
@jwt_required() # 要求有效Token[1,5](@ref)
|
||||
def protected():
|
||||
# 从Token获取用户身份
|
||||
current_user_id = get_jwt_identity()
|
||||
user = User.query.get(current_user_id)
|
||||
|
||||
return jsonify({
|
||||
"message": f"欢迎回来, {user.username}!",
|
||||
"user_id": user.id
|
||||
}), 200
|
||||
|
||||
|
||||
@jwt.unauthorized_loader
|
||||
def missing_token_callback(error):
|
||||
"""缺少Token时的自定义响应"""
|
||||
return jsonify({
|
||||
'status': 401,
|
||||
'msg': '缺少访问令牌'
|
||||
}), 401
|
||||
|
||||
|
||||
blacklist = set()
|
||||
|
||||
|
||||
@auth_bp.route('/createuser', methods=['POST'])
|
||||
@jwt_required() # 需要有效Token才能创建用户
|
||||
def create_user():
|
||||
'''
|
||||
{
|
||||
"username": "new_user",
|
||||
"password": "securePass123",
|
||||
"role_ids": [1, 3] // 要分配的角色ID列表
|
||||
}
|
||||
'''
|
||||
data = request.get_json()
|
||||
print(data)
|
||||
if not data or 'username' not in data or 'password' not in data:
|
||||
return jsonify({"error": "缺少用户名或密码"}), 400
|
||||
|
||||
# 1. 验证用户名唯一性
|
||||
existing_user = User.query.filter_by(username=data['username']).first()
|
||||
if existing_user:
|
||||
return jsonify({"error": "用户名已存在"}), 409
|
||||
|
||||
# 2. 获取当前操作者
|
||||
current_user_id = get_jwt_identity()
|
||||
print('current_user_id',current_user_id)
|
||||
current_user = User.query.get(int(current_user_id))
|
||||
if not current_user:
|
||||
return jsonify({"error": "无效的操作者"}), 401
|
||||
|
||||
# 3. 密码复杂度验证
|
||||
password = data['password']
|
||||
if len(password) < 8 or not any(c.isdigit() for c in password):
|
||||
return jsonify({"error": "密码需至少8位且包含数字"}), 400
|
||||
|
||||
# 4. 验证角色(新增部分)
|
||||
role_ids = data.get('role_ids', [])
|
||||
valid_roles = []
|
||||
if role_ids:
|
||||
valid_roles = Role.query.filter(Role.id.in_(role_ids)).all()
|
||||
if len(valid_roles) != len(role_ids):
|
||||
return jsonify({"error": "包含无效的角色ID"}), 400
|
||||
|
||||
try:
|
||||
# 5. 创建新用户
|
||||
new_user = User(
|
||||
username=data['username'],
|
||||
password=hash_password(data['password']),
|
||||
status=data.get('status', 1),
|
||||
created_by=current_user_id
|
||||
)
|
||||
|
||||
db.session.add(new_user)
|
||||
db.session.flush() # 获取新用户ID但不提交事务
|
||||
|
||||
# 6. 分配角色(新增核心功能)
|
||||
for role in valid_roles:
|
||||
user_role = UserRole(
|
||||
user_id=new_user.id,
|
||||
role_id=role.id,
|
||||
created_by=current_user_id
|
||||
)
|
||||
db.session.add(user_role)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# 7. 构造响应数据(包含角色信息)
|
||||
response_data = {
|
||||
"id": new_user.id,
|
||||
"username": new_user.username,
|
||||
"created_at": new_user.created_at.isoformat(),
|
||||
"created_by": new_user.created_by,
|
||||
"roles": [{"id": r.id, "name": r.name} for r in valid_roles] # 新增角色信息
|
||||
}
|
||||
|
||||
# 8. 记录操作日志(新增角色信息)
|
||||
current_app.logger.info(
|
||||
f"用户创建成功: {new_user.username} (ID:{new_user.id}) "
|
||||
f"操作者: {current_user.username} (ID:{current_user_id}) "
|
||||
f"分配角色: {[r.name for r in valid_roles]}" # 记录分配的角色
|
||||
)
|
||||
|
||||
return jsonify(response_data), 201
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(f"用户创建失败: {str(e)}")
|
||||
return jsonify({"error": "服务器内部错误"}), 500
|
||||
|
||||
|
||||
@auth_bp.route('/updateuser', methods=['POST'])
|
||||
@jwt_required() # 需要有效Token才能访问
|
||||
def update_user():
|
||||
'''
|
||||
请求体格式:
|
||||
{
|
||||
"user_id": 123, // 必需:目标用户ID
|
||||
"username": "updated_user", // 可选:新用户名
|
||||
"password": "newSecurePass456", // 可选:新密码
|
||||
"status": 1 // 可选:用户状态(1-启用,0-禁用)
|
||||
}
|
||||
'''
|
||||
data = request.get_json()
|
||||
if not data or 'user_id' not in data:
|
||||
return jsonify({"error": "请求数据不能为空且必须包含user_id"}), 400
|
||||
|
||||
# 1. 验证操作者身份
|
||||
current_user_id = get_jwt_identity()
|
||||
current_user = User.query.get(int(current_user_id))
|
||||
if not current_user:
|
||||
return jsonify({"error": "无效的操作者"}), 401
|
||||
|
||||
# 2. 验证目标用户是否存在
|
||||
target_user_id = data['user_id']
|
||||
target_user = User.query.get(int(target_user_id))
|
||||
if not target_user:
|
||||
return jsonify({"error": "目标用户不存在"}), 404
|
||||
|
||||
# 3. 权限校验:仅允许管理员修改(核心修改点)
|
||||
# 检查当前用户是否为管理员(假设管理员角色ID为1)
|
||||
is_admin = UserRole.query.filter_by( # 注意:这里修正为UserRole表(用户-角色关联表)
|
||||
user_id=current_user_id,
|
||||
role_id=1
|
||||
).first()
|
||||
|
||||
if not is_admin: # 只有管理员能通过校验
|
||||
return jsonify({"error": "仅管理员有权限修改用户信息"}), 403
|
||||
|
||||
role_ids = data.get('role_ids', None)
|
||||
valid_roles = []
|
||||
if role_ids is not None:
|
||||
if not isinstance(role_ids, list):
|
||||
return jsonify({"error": "role_ids必须为数组"}), 400
|
||||
if role_ids:
|
||||
valid_roles = Role.query.filter(Role.id.in_(role_ids)).all()
|
||||
if len(valid_roles) != len(role_ids):
|
||||
return jsonify({"error": "包含无效的角色ID"}), 400
|
||||
|
||||
try:
|
||||
# 4. 处理用户名修改(如提供)
|
||||
if 'username' in data and data['username']:
|
||||
# 验证用户名唯一性(排除自身)
|
||||
existing_user = User.query.filter(
|
||||
User.username == data['username'],
|
||||
User.id != target_user_id
|
||||
).first()
|
||||
if existing_user:
|
||||
return jsonify({"error": "用户名已存在"}), 409
|
||||
target_user.username = data['username']
|
||||
|
||||
# 5. 处理密码修改(如提供)
|
||||
if 'password' in data and data['password']:
|
||||
password = data['password']
|
||||
# 密码复杂度验证(与创建用户保持一致)
|
||||
if len(password) < 8 or not any(c.isdigit() for c in password):
|
||||
return jsonify({"error": "密码需至少8位且包含数字"}), 400
|
||||
target_user.password = hash_password(password) # 复用哈希函数
|
||||
|
||||
# 6. 处理状态修改(如提供)
|
||||
if 'status' in data:
|
||||
target_user.status = data['status'] # 管理员无需额外校验
|
||||
|
||||
# 6.1 处理角色修改(如提供)
|
||||
if role_ids is not None:
|
||||
UserRole.query.filter_by(user_id=target_user_id).delete()
|
||||
for role in valid_roles:
|
||||
user_role = UserRole(
|
||||
user_id=target_user_id,
|
||||
role_id=role.id,
|
||||
created_by=current_user_id,
|
||||
)
|
||||
db.session.add(user_role)
|
||||
|
||||
# 7. 提交修改
|
||||
target_user.updated_at = datetime.now() # 假设模型有updated_at字段
|
||||
target_user.updated_by = current_user_id # 假设模型有updated_by字段
|
||||
db.session.commit()
|
||||
|
||||
# 8. 构造响应数据
|
||||
response_data = {
|
||||
"id": target_user.id,
|
||||
"username": target_user.username,
|
||||
"status": target_user.status,
|
||||
"updated_at": target_user.updated_at.isoformat(),
|
||||
"updated_by": current_user.username
|
||||
}
|
||||
if role_ids is not None:
|
||||
response_data["roles"] = [{"id": r.id, "name": r.name} for r in valid_roles]
|
||||
|
||||
# 9. 记录操作日志
|
||||
current_app.logger.info(
|
||||
f"用户信息更新成功: {target_user.username} (ID:{target_user.id}) "
|
||||
f"操作者: {current_user.username} (ID:{current_user_id}) "
|
||||
f"修改内容: {[k for k in data.keys() if k != 'user_id']}"
|
||||
)
|
||||
|
||||
return jsonify(response_data), 200
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(f"用户信息更新失败: {str(e)}")
|
||||
return jsonify({"error": "服务器内部错误"}), 500
|
||||
|
||||
|
||||
@auth_bp.route('/logout', methods=['POST'])
|
||||
@jwt_required()
|
||||
def log_out():
|
||||
jti = get_jwt()["jti"] # 获取 Token 的唯一标识
|
||||
blacklist.add(jti) # 将 Token 添加到黑名单
|
||||
return jsonify({"msg": "Successfully logged out"}), 200
|
||||
|
||||
|
||||
@auth_bp.route('/getuserlist', methods=['GET'])
|
||||
@jwt_required()
|
||||
def get_users():
|
||||
result = db.session.query(
|
||||
User.id,
|
||||
User.username,
|
||||
User.status,
|
||||
User.created_at,
|
||||
User.created_by,
|
||||
User.updated_at,
|
||||
UserRole.role_id,
|
||||
Role.description,
|
||||
Role.name
|
||||
).join(
|
||||
UserRole, User.id == UserRole.user_id
|
||||
).join(
|
||||
Role, UserRole.role_id == Role.id
|
||||
).all()
|
||||
dict_list = [row._asdict() for row in result]
|
||||
return jsonify(dict_list), 200
|
||||
|
||||
|
||||
@auth_bp.route('/getroles', methods=['GET'])
|
||||
@jwt_required()
|
||||
def get_roles():
|
||||
roles = Role.query.filter(Role.status == 1).all()
|
||||
data = [{"id": r.id, "name": r.name, "description": r.description} for r in roles]
|
||||
return jsonify(data), 200
|
||||
|
||||
|
||||
@auth_bp.route('/deleteuser', methods=['GET'])
|
||||
@jwt_required()
|
||||
def delete_user():
|
||||
|
||||
user_id=request.args.get('userid')
|
||||
# 1. 查询用户(确保状态之前为 1)
|
||||
user = User.query.filter(
|
||||
User.id == user_id,
|
||||
User.status == 1 # 只选择状态为 1 的用户
|
||||
).first()
|
||||
|
||||
if not user:
|
||||
return jsonify({"error": "用户不存在或状态不是 1"}), 400 # 用户不存在或状态不是 1
|
||||
|
||||
# 2. 修改状态
|
||||
user.status = 0
|
||||
|
||||
# 3. 提交事务
|
||||
try:
|
||||
db.session.commit()
|
||||
return jsonify({"msg": "Successfully"}),200
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"Error: {e}")
|
||||
return jsonify({"error": f"出现错误{e}"}), 400
|
||||
|
||||
|
||||
@auth_bp.route('/test', methods=['GET'])
|
||||
@jwt_required()
|
||||
def test():
|
||||
pass
|
||||
|
||||
|
||||
@auth_bp.route('/gethisttory', methods=['POST'])
|
||||
@jwt_required()
|
||||
def query_operation_history():
|
||||
"""
|
||||
{
|
||||
"username": "admin", // 可选,精确匹配用户名
|
||||
"api_path": "/api/user/create", // 可选,精确匹配接口路径
|
||||
"start_time": "2025-07-01T00:00:00", // 可选,开始时间(ISO 8601格式)
|
||||
"end_time": "2025-07-10T23:59:59", // 可选,结束时间(ISO 8601格式)
|
||||
"request_keyword": "email", // 可选,在请求参数中模糊搜索关键词
|
||||
"page": 2, // 可选,页码(默认1)
|
||||
"per_page": 15 // 可选,每页条数(默认20)
|
||||
}
|
||||
"""
|
||||
|
||||
"""查询操作历史记录(支持多条件过滤)"""
|
||||
# 接收JSON格式的查询参数
|
||||
query_params = request.get_json()
|
||||
|
||||
# 构建基础查询
|
||||
query = OperationHistory.query
|
||||
|
||||
# 1. 用户名过滤(精确匹配)
|
||||
if query_params['username']:
|
||||
query = query.filter(OperationHistory.username == query_params['username'])
|
||||
|
||||
# 2. 接口路径过滤(精确匹配)
|
||||
if query_params['api_path']:
|
||||
query = query.filter(OperationHistory.api_path == query_params['api_path'])
|
||||
|
||||
# 3. 操作时间范围过滤
|
||||
if query_params['start_time'] and query_params['end_time']:
|
||||
# 解析时间参数(支持ISO 8601格式)
|
||||
start_time = datetime.fromisoformat(query_params['start_time']) if 'start_time' in query_params else None
|
||||
end_time = datetime.fromisoformat(query_params['end_time']) if 'end_time' in query_params else None
|
||||
# 构建时间范围查询条件
|
||||
time_conditions = []
|
||||
if start_time:
|
||||
time_conditions.append(OperationHistory.operation_time >= start_time)
|
||||
if end_time:
|
||||
time_conditions.append(OperationHistory.operation_time <= end_time)
|
||||
|
||||
if time_conditions:
|
||||
query = query.filter(and_(*time_conditions))
|
||||
|
||||
# 4. 请求参数关键词过滤(模糊匹配)
|
||||
if query_params['request_keyword']:
|
||||
keyword = f"%{query_params['request_keyword']}%"
|
||||
query = query.filter(
|
||||
OperationHistory.request_params.like(keyword)
|
||||
)
|
||||
|
||||
# 5. 分页参数处理
|
||||
page = query_params.get('page', 1)
|
||||
per_page = query_params.get('per_page', 20)
|
||||
|
||||
# 执行分页查询(使用索引优化)
|
||||
pagination = query.order_by(OperationHistory.operation_time.desc()) \
|
||||
.paginate(page=page, per_page=per_page, error_out=False)
|
||||
|
||||
# 序列化结果
|
||||
results = []
|
||||
for record in pagination.items:
|
||||
results.append({
|
||||
'id': record.id,
|
||||
'username': record.username,
|
||||
'api_path': record.api_path,
|
||||
'http_method': record.http_method,
|
||||
'operation_time': record.operation_time.isoformat(),
|
||||
'response_code': record.response_code,
|
||||
'ip_address': record.ip_address,
|
||||
'operation_result': record.operation_result,
|
||||
'error_message': record.error_message,
|
||||
'trace_id': record.trace_id,
|
||||
# 请求参数反序列化(JSON字符串→Python对象)
|
||||
'request_params': json.loads(record.request_params) if record.request_params else None
|
||||
})
|
||||
|
||||
# 返回分页结果
|
||||
return jsonify({
|
||||
'total': pagination.total,
|
||||
'pages': pagination.pages,
|
||||
'current_page': pagination.page,
|
||||
'per_page': pagination.per_page,
|
||||
'results': results
|
||||
})
|
||||
0
app/blueprints/auth/services.py
Normal file
0
app/blueprints/auth/services.py
Normal file
5
app/blueprints/bag_data/__init__.py
Normal file
5
app/blueprints/bag_data/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# from routes import Blueprint
|
||||
|
||||
# posts_bp = Blueprint('posts', __name__)
|
||||
|
||||
# from . import routes
|
||||
55
app/blueprints/bag_data/routes.py
Normal file
55
app/blueprints/bag_data/routes.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
from flask import Blueprint, current_app, request, jsonify
|
||||
from flask_jwt_extended import create_access_token, get_jwt, get_jwt_identity, jwt_required
|
||||
from sqlalchemy import and_, asc, desc, exists, func, or_
|
||||
from app.models import BagFile, Fst, Role, User, UserRole
|
||||
from app.utils.fst_tree import build_tree,build_sub_tree
|
||||
from app import db
|
||||
from app.utils.log_record import log_operation
|
||||
from sqlalchemy.orm import aliased
|
||||
|
||||
# 1.创建蓝图实例,指定名称和导入名称
|
||||
data_bp = Blueprint('bag', __name__)
|
||||
|
||||
@data_bp.route('/fstlist',methods=['GET'])
|
||||
@jwt_required()
|
||||
def get_fstlist():
|
||||
res=Fst.query.with_entities(Fst.id,Fst.name,Fst.parent_id).all()
|
||||
response=build_tree(res)
|
||||
return jsonify(response), 200
|
||||
|
||||
|
||||
|
||||
@data_bp.route('/fstlevel1', methods=['GET'])
|
||||
def get_level1_fst():
|
||||
# 查询 level=1 的记录,仅返回 id, name, level 字段
|
||||
results = Fst.query.with_entities(
|
||||
Fst.id,
|
||||
Fst.name,
|
||||
Fst.level
|
||||
).filter_by(level=1).all()
|
||||
|
||||
# 转换为字典列表(方便 JSON 序列化)
|
||||
data = [{
|
||||
"id": r.id,
|
||||
"name": r.name,
|
||||
"level": r.level
|
||||
} for r in results]
|
||||
|
||||
return jsonify(data),200
|
||||
|
||||
# 跨表查询所有的bag
|
||||
|
||||
@data_bp.route('/getnode', methods=['GET'])
|
||||
@jwt_required()
|
||||
def query_sub_node():
|
||||
'''
|
||||
根据一级标签查询对应的所有子标签
|
||||
'''
|
||||
tagid=request.args.get('tagid')
|
||||
res=Fst.query.with_entities(Fst.id,Fst.name,Fst.parent_id).all()
|
||||
response=build_sub_tree(res,tagid)
|
||||
return jsonify(response), 200
|
||||
|
||||
|
||||
73
app/blueprints/bag_data/services.py
Normal file
73
app/blueprints/bag_data/services.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# from datetime import datetime,timedelta
|
||||
# from app import db
|
||||
# from app.models import BagRecord
|
||||
# from sqlalchemy.exc import SQLAlchemyError
|
||||
# from sqlalchemy import func
|
||||
|
||||
# def add():
|
||||
# new_record = BagRecord(
|
||||
# bag_name="sensor_data_2025.bag",
|
||||
# collection_time=datetime(2025, 5, 1, 12, 1, 1),
|
||||
# bag_state=0,
|
||||
# frames=120,
|
||||
# car_state=1,
|
||||
# address="beijing",
|
||||
# collect_month=5
|
||||
# )
|
||||
# db.session.add(new_record)
|
||||
# db.session.commit() # 提交事务 [1,7](@ref)
|
||||
|
||||
|
||||
# def create_record(data):
|
||||
# """创建新记录"""
|
||||
# try:
|
||||
# # 转换日期类型字段
|
||||
# if 'collection_time' in data:
|
||||
# data['collection_time'] = datetime.fromisoformat(data['collection_time'])
|
||||
# if 'parse_time' in data:
|
||||
# data['parse_time'] = datetime.fromisoformat(data['parse_time'])
|
||||
|
||||
# record = BagRecord(**data)
|
||||
# db.session.add(record)
|
||||
# db.session.commit()
|
||||
# return record
|
||||
# except SQLAlchemyError as e:
|
||||
# db.session.rollback()
|
||||
# raise e
|
||||
|
||||
# def get_record_by_id(record_id):
|
||||
# """根据ID获取记录"""
|
||||
# return BagRecord.query.get(record_id)
|
||||
|
||||
|
||||
# def get_all_records(**filters):
|
||||
# """获取所有记录(支持过滤)"""
|
||||
# query = BagRecord.query
|
||||
# if filters:
|
||||
# query = query.filter_by(**filters)
|
||||
# return query.all()
|
||||
|
||||
|
||||
# def get_daily_counts(start_date, end_date):
|
||||
# # 执行 SQL 查询,按日期分组并统计数量
|
||||
# query = (
|
||||
# db.session.query(
|
||||
# db.func.date(BagRecord.collection_time).label('date'),
|
||||
# db.func.count(BagRecord.id).label('count')
|
||||
# )
|
||||
# .filter(BagRecord.collection_time >= start_date)
|
||||
# .filter(BagRecord.collection_time <= end_date + timedelta(days=1)) # 包含结束日期
|
||||
# .group_by(db.func.date(BagRecord.collection_time))
|
||||
# .order_by(db.func.date(BagRecord.collection_time))
|
||||
# )
|
||||
|
||||
# print(query.all())
|
||||
# # 将结果转换为字典列表
|
||||
# results = []
|
||||
# for date, count in query.all():
|
||||
# results.append({
|
||||
# 'date': str(date), # 将日期对象转换为字符串
|
||||
# 'count': count
|
||||
# })
|
||||
|
||||
# return results
|
||||
0
app/blueprints/data_display/__init__.py
Normal file
0
app/blueprints/data_display/__init__.py
Normal file
752
app/blueprints/data_display/routes.py
Normal file
752
app/blueprints/data_display/routes.py
Normal file
@@ -0,0 +1,752 @@
|
||||
from datetime import datetime
|
||||
from flask import Blueprint, current_app, request, jsonify
|
||||
from flask_jwt_extended import (
|
||||
create_access_token,
|
||||
get_jwt,
|
||||
get_jwt_identity,
|
||||
jwt_required,
|
||||
)
|
||||
from sqlalchemy import and_, asc, desc, exists, or_,func
|
||||
from app.models import (
|
||||
BagFile,
|
||||
BagStatus,
|
||||
Fst,
|
||||
QaStatus,
|
||||
Role,
|
||||
SyncStatus,
|
||||
TaggingEvents,
|
||||
User,
|
||||
UserRole,
|
||||
BagMergeRecord,
|
||||
)
|
||||
from app.utils.fst_tree import build_tree
|
||||
from app import db
|
||||
from app.utils.log_record import log_operation
|
||||
from requests.exceptions import RequestException
|
||||
import requests
|
||||
from sqlalchemy.orm import aliased, joinedload
|
||||
|
||||
check_data = Blueprint("display", __name__)
|
||||
|
||||
def serialize_tagging_event(event: TaggingEvents) -> dict:
|
||||
def safe_name(tag):
|
||||
return getattr(tag, "name", None) if tag else None
|
||||
|
||||
level1 = safe_name(event.level1_tag)
|
||||
level2 = safe_name(event.level2_tag)
|
||||
level3 = safe_name(event.level3_tag)
|
||||
level4 = safe_name(event.level4_tag)
|
||||
tag_path = " / ".join([x for x in [level1, level2, level3, level4] if x])
|
||||
return {
|
||||
"event_id": event.event_id,
|
||||
"bag_id": event.bag_id,
|
||||
"tag_path": tag_path,
|
||||
"level1_tag_id": event.level1_tag_id,
|
||||
"level1_tag_name": level1,
|
||||
"level2_tag_id": event.level2_tag_id,
|
||||
"level2_tag_name": level2,
|
||||
"level3_tag_id": event.level3_tag_id,
|
||||
"level3_tag_name": level3,
|
||||
"level4_tag_id": event.level4_tag_id,
|
||||
"level4_tag_name": level4,
|
||||
"qa_status": event.qa_status.value if event.qa_status else None,
|
||||
"case_type": event.case_type,
|
||||
"front_starttime": event.front_starttime.isoformat()
|
||||
if event.front_starttime
|
||||
else None,
|
||||
"front_endtime": event.front_endtime.isoformat() if event.front_endtime else None,
|
||||
"front_start_sec": event.front_start_sec,
|
||||
"front_end_sec": event.front_end_sec,
|
||||
"high_speed": event.high_speed,
|
||||
"urban": event.urban,
|
||||
"parking": event.parking,
|
||||
"note": event.note,
|
||||
"source": event.source.value if event.source else None,
|
||||
"reviewer_id": event.reviewer_id,
|
||||
"ts_event": event.ts_event.isoformat() if event.ts_event else None,
|
||||
}
|
||||
|
||||
|
||||
@check_data.route("/bagtotal", methods=["GET"])
|
||||
@jwt_required()
|
||||
def bag_total():
|
||||
"""查询BagFile表记录总数"""
|
||||
# 执行SELECT count(*) FROM bag_file
|
||||
total_count = BagFile.query.count()
|
||||
|
||||
return jsonify({"total": total_count}), 200
|
||||
|
||||
|
||||
@check_data.route("/noprocess-bagtotal", methods=["GET"])
|
||||
@jwt_required()
|
||||
def no_process_bag_total():
|
||||
count = BagFile.query.filter(BagFile.operation_status == 0).count()
|
||||
return jsonify({"total": count}), 200
|
||||
|
||||
|
||||
@check_data.route("/insert-qa-status", methods=["POST"])
|
||||
@jwt_required()
|
||||
def insert_qa_status():
|
||||
try:
|
||||
# 获取请求数据
|
||||
data = request.get_json()
|
||||
current_user = get_jwt_identity()
|
||||
|
||||
# 验证必要参数(bag_id、qa_status为必填,评论为可选)
|
||||
required_fields = ["bag_id", "qa_status"]
|
||||
if not data or not all(field in data for field in required_fields):
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"success": False,
|
||||
"message": f"缺少必要参数,需要: {required_fields}",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
# 验证qa_status是否为有效值
|
||||
try:
|
||||
qa_status = QaStatus(data["qa_status"])
|
||||
except ValueError:
|
||||
valid_statuses = [status.value for status in QaStatus]
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"success": False,
|
||||
"message": f"无效的qa_status值,有效值为: {valid_statuses}",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
# 查询对应的BagFile记录
|
||||
bag_file = BagFile.query.get(data["bag_id"])
|
||||
if not bag_file:
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"success": False,
|
||||
"message": f'未找到ID为{data["bag_id"]}的BagFile记录',
|
||||
}
|
||||
),
|
||||
404,
|
||||
)
|
||||
|
||||
# 仅汇总 TaggingEvents,不覆盖每条事件的 qa_status
|
||||
tag_events = TaggingEvents.query.filter_by(
|
||||
bag_id=bag_file.id, is_deleted=False
|
||||
).all()
|
||||
|
||||
def aggregate_qa_status(events):
|
||||
has_not_reviewed = False
|
||||
has_invalid = False
|
||||
has_modify = False
|
||||
has_passed = False
|
||||
for ev in events:
|
||||
status = ev.qa_status or QaStatus.QA_NOT_REVIEWED
|
||||
if status == QaStatus.QA_NOT_REVIEWED:
|
||||
has_not_reviewed = True
|
||||
elif status == QaStatus.QA_INVALID:
|
||||
has_invalid = True
|
||||
elif status == QaStatus.QA_MODIFY:
|
||||
has_modify = True
|
||||
elif status == QaStatus.QA_PASSED:
|
||||
has_passed = True
|
||||
if has_not_reviewed:
|
||||
return QaStatus.QA_NOT_REVIEWED
|
||||
if has_invalid:
|
||||
return QaStatus.QA_INVALID
|
||||
if has_modify:
|
||||
return QaStatus.QA_MODIFY
|
||||
if has_passed:
|
||||
return QaStatus.QA_PASSED
|
||||
return QaStatus.QA_NOT_REVIEWED
|
||||
|
||||
# 更新字段(包含评论字段)
|
||||
if tag_events:
|
||||
bag_file.qa_status = aggregate_qa_status(tag_events)
|
||||
bag_file.qa_confirm_time = datetime.now()
|
||||
bag_file.qa_id = current_user
|
||||
if "bag_status" in data:
|
||||
bag_file.bag_status = data.get("bag_status")
|
||||
|
||||
# 处理评论字段(使用get方法避免参数不存在时报错,默认空字符串)
|
||||
bag_file.comment1 = data.get("comment1") if data.get("comment1") else None
|
||||
bag_file.comment2 = data.get("comment2") if data.get("comment2") else None
|
||||
bag_file.comment3 = data.get("comment3") if data.get("comment3") else None
|
||||
|
||||
# 提交到数据库
|
||||
db.session.commit()
|
||||
|
||||
final_qa_status = bag_file.qa_status.value if bag_file.qa_status else None
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
"success": True,
|
||||
"message": f'BagFile ID {data["bag_id"]} 的qa_status已更新为 {final_qa_status}',
|
||||
"data": {
|
||||
"bag_id": data["bag_id"],
|
||||
"qa_status": final_qa_status,
|
||||
"bag_status": data.get("bag_status"),
|
||||
"comments": {
|
||||
"comment1": bag_file.comment1,
|
||||
"comment2": bag_file.comment2,
|
||||
"comment3": bag_file.comment3,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
# 发生错误时回滚
|
||||
db.session.rollback()
|
||||
return jsonify({"success": False, "message": f"更新失败: {str(e)}"}), 500
|
||||
|
||||
|
||||
@check_data.route("/get-finish-list", methods=["POST"])
|
||||
@jwt_required()
|
||||
def get_processed():
|
||||
try:
|
||||
# 1. 解析请求参数(新增sync_status参数)
|
||||
params = request.get_json() or {}
|
||||
print(f"【请求参数】原始参数: {params}")
|
||||
|
||||
page = params.get("page", 1)
|
||||
per_page = params.get("per_page", 20)
|
||||
file_name = params.get("file_name", "").strip()
|
||||
start_datetime = params.get("start_datetime", "").strip()
|
||||
end_datetime = params.get("end_datetime", "").strip()
|
||||
level1_tag = params.get("level1_tag", "")
|
||||
status_str = params.get("status", "").strip()
|
||||
user_id = params.get("user_id", "")
|
||||
# sync_status_str = params.get('sync_status', '').strip() # 新增:同步状态参数
|
||||
|
||||
# 2. merge 表别名
|
||||
M = aliased(BagMergeRecord)
|
||||
|
||||
# 3. 构建基础查询:
|
||||
# 选出 BagFile + 逻辑文件名 logical_name = coalesce(joined_name, file_name)
|
||||
logical_name = func.coalesce(M.joined_name, BagFile.file_name)
|
||||
|
||||
# 2. 构建基础查询(关联User表用于查询username)
|
||||
query = (
|
||||
db.session.query(
|
||||
BagFile,
|
||||
logical_name.label("logical_name"),
|
||||
)
|
||||
.outerjoin(User, BagFile.user_id == User.id)
|
||||
.outerjoin(M, M.src_name == BagFile.file_name)
|
||||
.options(
|
||||
joinedload(BagFile.level1_tag),
|
||||
joinedload(BagFile.level2_tag),
|
||||
joinedload(BagFile.level3_tag),
|
||||
joinedload(BagFile.level4_tag),
|
||||
)
|
||||
)
|
||||
|
||||
# 3. 动态添加过滤条件
|
||||
conditions = []
|
||||
|
||||
# 固定返回bag_status>=1的数据
|
||||
conditions.append(BagFile.bag_status >= 1)
|
||||
conditions.append(BagFile.sync_status == "SYNCED")
|
||||
|
||||
# 只保留 merge 主 bag 或未 merge 的 bag
|
||||
conditions.append(
|
||||
or_(
|
||||
M.is_initiator == True, # 参与 merge 的主 bag
|
||||
M.id.is_(None), # 完全没 merge 过的 bag(outer join 不命中,M.* 为 NULL)
|
||||
)
|
||||
)
|
||||
|
||||
# 根据user_id过滤
|
||||
if user_id:
|
||||
try:
|
||||
user_id_int = int(user_id)
|
||||
conditions.append(BagFile.user_id == user_id_int)
|
||||
except ValueError:
|
||||
print(f"【过滤条件】user_id格式错误(非整数): {user_id}")
|
||||
|
||||
# 新增:根据sync_status过滤
|
||||
# if sync_status_str:
|
||||
# try:
|
||||
# sync_status_enum = SyncStatus[sync_status_str] # 假设SyncStatus是定义的枚举类
|
||||
# conditions.append(BagFile.sync_status == sync_status_enum)
|
||||
# except KeyError:
|
||||
# print(f"【过滤条件】无效的sync_status值: {sync_status_str}")
|
||||
|
||||
# 文件名模糊查询
|
||||
if file_name:
|
||||
conditions.append(BagFile.file_name.like(f"%{file_name}%"))
|
||||
print(f"【过滤条件】添加文件名模糊查询: {file_name}")
|
||||
|
||||
# 时间范围查询
|
||||
if start_datetime:
|
||||
try:
|
||||
start_dt = datetime.fromisoformat(start_datetime)
|
||||
conditions.append(BagFile.capture_datetime >= start_dt)
|
||||
except ValueError:
|
||||
print(f"【过滤条件】开始时间格式错误: {start_datetime}")
|
||||
|
||||
if end_datetime:
|
||||
try:
|
||||
end_dt = datetime.fromisoformat(end_datetime)
|
||||
conditions.append(BagFile.capture_datetime <= end_dt)
|
||||
except ValueError:
|
||||
print(f"【过滤条件】结束时间格式错误: {end_datetime}")
|
||||
|
||||
# 一级标签查询
|
||||
if level1_tag:
|
||||
try:
|
||||
level1_id = int(level1_tag)
|
||||
conditions.append(BagFile.level1_tag_id == level1_id)
|
||||
except ValueError:
|
||||
print(f"【过滤条件】level1_tag转换失败(非整数): {level1_tag}")
|
||||
|
||||
# 状态查询
|
||||
if status_str:
|
||||
try:
|
||||
status_enum = BagStatus[status_str]
|
||||
conditions.append(BagFile.status == status_enum)
|
||||
except KeyError:
|
||||
print(f"【过滤条件】无效状态值: {status_str}")
|
||||
|
||||
# 应用所有条件
|
||||
query = query.filter(and_(*conditions))
|
||||
|
||||
# 4. 排序:按update_time降序(核心调整)
|
||||
query = query.order_by(BagFile.qa_confirm_time.desc())
|
||||
|
||||
# 5. 分页查询
|
||||
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
total_items = pagination.total
|
||||
total_pages = pagination.pages
|
||||
|
||||
# 6. 序列化(新增sync_status字段)
|
||||
bag_files = []
|
||||
bag_ids = [bag.id for bag, _ in pagination.items]
|
||||
tag_events_by_bag = {}
|
||||
if bag_ids:
|
||||
tag_events = (
|
||||
TaggingEvents.query.options(
|
||||
joinedload(TaggingEvents.level1_tag),
|
||||
joinedload(TaggingEvents.level2_tag),
|
||||
joinedload(TaggingEvents.level3_tag),
|
||||
joinedload(TaggingEvents.level4_tag),
|
||||
)
|
||||
.filter(
|
||||
TaggingEvents.bag_id.in_(bag_ids),
|
||||
TaggingEvents.is_deleted == False,
|
||||
)
|
||||
.order_by(TaggingEvents.bag_id.asc(), TaggingEvents.ts_event.asc())
|
||||
.all()
|
||||
)
|
||||
for ev in tag_events:
|
||||
tag_events_by_bag.setdefault(ev.bag_id, []).append(
|
||||
serialize_tagging_event(ev)
|
||||
)
|
||||
|
||||
for bag ,logical_name_value in pagination.items:
|
||||
qa_username = (
|
||||
bag.qa_user.username if (bag.qa_user and bag.qa_user.username) else ""
|
||||
)
|
||||
bag_files.append(
|
||||
{
|
||||
"id": bag.id,
|
||||
"file_name": logical_name_value,
|
||||
"capture_datetime": (
|
||||
bag.capture_datetime.isoformat()
|
||||
if bag.capture_datetime
|
||||
else None
|
||||
),
|
||||
"status": bag.status.value if bag.status else None,
|
||||
"create_time": (
|
||||
bag.create_time.isoformat() if bag.create_time else None
|
||||
),
|
||||
"update_time": (
|
||||
bag.qa_confirm_time.isoformat() if bag.qa_confirm_time else None
|
||||
), # 排序字段显示
|
||||
"level1_tag": bag.level1_tag.name if bag.level1_tag else None,
|
||||
"level2_tag": bag.level2_tag.name if bag.level2_tag else None,
|
||||
"level3_tag": bag.level3_tag.name if bag.level3_tag else None,
|
||||
"level4_tag": bag.level4_tag.name if bag.level4_tag else None,
|
||||
"bag_status": bag.bag_status,
|
||||
"user_id": bag.user_id,
|
||||
"username": (
|
||||
bag.user.username if (bag.user and bag.user.username) else ""
|
||||
),
|
||||
"qa_status": bag.qa_status.value,
|
||||
"sync_status": (
|
||||
bag.sync_status.value if bag.sync_status else None
|
||||
), # 新增:同步状态
|
||||
"qa_user_id": bag.qa_id, # 可选:返回QA操作人ID
|
||||
"qa_username": qa_username,
|
||||
"comment1": bag.comment1,
|
||||
"front_start_sec": bag.front_start_sec,
|
||||
"front_end_sec": bag.front_end_sec,
|
||||
"high_speed": bag.high_speed,
|
||||
"urban": bag.urban,
|
||||
"parking": bag.parking,
|
||||
"tag_events": tag_events_by_bag.get(bag.id, []),
|
||||
}
|
||||
)
|
||||
|
||||
# 7. 返回响应
|
||||
return jsonify(
|
||||
{
|
||||
"code": 200,
|
||||
"data": bag_files,
|
||||
"total": total_items,
|
||||
"page": page,
|
||||
"pages": total_pages,
|
||||
"message": "查询成功(包含sync_status过滤及按update_time排序)",
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"【接口异常】: {str(e)}")
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"code": 500,
|
||||
"data": [],
|
||||
"total": 0,
|
||||
"page": page if "page" in locals() else 1,
|
||||
"pages": 0,
|
||||
"message": f"查询出错:{str(e)}",
|
||||
}
|
||||
),
|
||||
500,
|
||||
)
|
||||
|
||||
|
||||
@check_data.route("/insert-db-ids", methods=["POST"])
|
||||
@jwt_required()
|
||||
def insert_db_ids():
|
||||
try:
|
||||
# 获取请求体中的ID列表
|
||||
id_list = request.get_json()
|
||||
|
||||
# 1. 验证参数格式
|
||||
if not isinstance(id_list, list):
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"success": False,
|
||||
"message": "参数格式错误,需提供数组格式,如[1,2,3]",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
# 检查列表中是否全为整数
|
||||
if not all(isinstance(id_val, int) for id_val in id_list):
|
||||
return jsonify({"success": False, "message": "数组元素必须为整数ID"}), 400
|
||||
|
||||
# 检查列表是否为空
|
||||
if len(id_list) == 0:
|
||||
return jsonify({"success": False, "message": "ID列表不能为空"}), 400
|
||||
|
||||
# 2. 执行批量更新
|
||||
current_time = datetime.now()
|
||||
# 使用SQLAlchemy的批量更新
|
||||
updated_rows = BagFile.query.filter(BagFile.id.in_(id_list)).update(
|
||||
{BagFile.sync_status: "SYNCED", BagFile.qa_confirm_time: current_time},
|
||||
synchronize_session=False,
|
||||
)
|
||||
|
||||
# 提交事务
|
||||
db.session.commit()
|
||||
|
||||
# 3. 返回结果
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"success": True,
|
||||
"message": f"成功更新{updated_rows}条记录",
|
||||
"updated_count": updated_rows,
|
||||
"updated_ids": id_list,
|
||||
}
|
||||
),
|
||||
200,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
# 发生错误时回滚事务
|
||||
db.session.rollback()
|
||||
return jsonify({"success": False, "message": f"更新失败:{str(e)}"}), 500
|
||||
|
||||
|
||||
@check_data.route("/insert-rootdb", methods=["POST"])
|
||||
@jwt_required()
|
||||
def insert_rootdb():
|
||||
API_URL = "http://10.0.240.4:5232/api/fst/bags/update"
|
||||
TIMEOUT = 10
|
||||
result = {"success_count": 0, "fail_count": 0, "fail_details": []}
|
||||
|
||||
try:
|
||||
param_list = request.get_json()
|
||||
# print(f"【insert_rootdb】请求参数: {param_list}")
|
||||
if not param_list or not isinstance(param_list, list):
|
||||
return jsonify({"success": False, "message": "请求参数必须为非空数组"}), 400
|
||||
|
||||
bag_ids = [
|
||||
item.get("rowid")
|
||||
for item in param_list
|
||||
if isinstance(item, dict) and "rowid" in item
|
||||
]
|
||||
bag_files = BagFile.query.filter(BagFile.id.in_(bag_ids)).all() if bag_ids else []
|
||||
bag_by_id = {bag.id: bag for bag in bag_files}
|
||||
merge_name_by_src = {}
|
||||
if bag_files:
|
||||
src_names = [bag.file_name for bag in bag_files if bag.file_name]
|
||||
if src_names:
|
||||
merge_records = (
|
||||
BagMergeRecord.query.filter(BagMergeRecord.src_name.in_(src_names))
|
||||
.filter(BagMergeRecord.is_initiator == True)
|
||||
.all()
|
||||
)
|
||||
for record in merge_records:
|
||||
merge_name_by_src[record.src_name] = record.joined_name
|
||||
|
||||
tag_events = (
|
||||
TaggingEvents.query.options(
|
||||
joinedload(TaggingEvents.level1_tag),
|
||||
joinedload(TaggingEvents.level2_tag),
|
||||
joinedload(TaggingEvents.level3_tag),
|
||||
joinedload(TaggingEvents.level4_tag),
|
||||
)
|
||||
.filter(
|
||||
TaggingEvents.bag_id.in_(bag_ids),
|
||||
TaggingEvents.is_deleted == False,
|
||||
)
|
||||
.order_by(TaggingEvents.bag_id.asc(), TaggingEvents.ts_event.asc())
|
||||
.all()
|
||||
if bag_ids
|
||||
else []
|
||||
)
|
||||
tag_events_by_bag = {}
|
||||
for ev in tag_events:
|
||||
tag_events_by_bag.setdefault(ev.bag_id, []).append(ev)
|
||||
|
||||
def _tags_from_flags(high_speed, urban, parking):
|
||||
tags = []
|
||||
if high_speed == 1:
|
||||
tags.append("highway")
|
||||
if urban == 1:
|
||||
tags.append("city")
|
||||
if parking == 0:
|
||||
tags.append("driving")
|
||||
return tags
|
||||
|
||||
def _nodes_and_level(level1, level2, level3, level4):
|
||||
nodes = [level1 or "", level2 or "", level3 or "", level4 or ""]
|
||||
first_empty_index = next(
|
||||
(idx for idx, node in enumerate(nodes) if node == ""), -1
|
||||
)
|
||||
if first_empty_index == -1:
|
||||
fst_node_level = len(nodes) - 1
|
||||
else:
|
||||
fst_node_level = first_empty_index - 1
|
||||
return nodes, fst_node_level
|
||||
|
||||
def _build_payload(
|
||||
bag_name,
|
||||
level1,
|
||||
level2,
|
||||
level3,
|
||||
level4,
|
||||
start_sec,
|
||||
end_sec,
|
||||
comments,
|
||||
high_speed,
|
||||
urban,
|
||||
parking,
|
||||
):
|
||||
nodes, fst_node_level = _nodes_and_level(level1, level2, level3, level4)
|
||||
return {
|
||||
"bag_name": bag_name,
|
||||
"nodes": nodes,
|
||||
"start_time": start_sec if start_sec is not None else 0,
|
||||
"end_time": end_sec if end_sec is not None else 0,
|
||||
"comments": comments or "",
|
||||
"tags": _tags_from_flags(high_speed, urban, parking),
|
||||
"fst_node_level": fst_node_level,
|
||||
}
|
||||
|
||||
def _safe_name(tag):
|
||||
return tag.name if tag else ""
|
||||
|
||||
def _payload_from_event(event, bag_name):
|
||||
return _build_payload(
|
||||
bag_name,
|
||||
_safe_name(event.level1_tag),
|
||||
_safe_name(event.level2_tag),
|
||||
_safe_name(event.level3_tag),
|
||||
_safe_name(event.level4_tag),
|
||||
event.front_start_sec,
|
||||
event.front_end_sec,
|
||||
event.note,
|
||||
event.high_speed,
|
||||
event.urban,
|
||||
event.parking,
|
||||
)
|
||||
|
||||
def _payload_from_bag(bag, bag_name):
|
||||
return _build_payload(
|
||||
bag_name,
|
||||
_safe_name(bag.level1_tag),
|
||||
_safe_name(bag.level2_tag),
|
||||
_safe_name(bag.level3_tag),
|
||||
_safe_name(bag.level4_tag),
|
||||
bag.front_start_sec,
|
||||
bag.front_end_sec,
|
||||
bag.comment1,
|
||||
bag.high_speed,
|
||||
bag.urban,
|
||||
bag.parking,
|
||||
)
|
||||
|
||||
for idx, item in enumerate(param_list):
|
||||
bag_failures = []
|
||||
try:
|
||||
if not isinstance(item, dict):
|
||||
raise ValueError("单条数据必须为对象")
|
||||
if "rowid" not in item:
|
||||
raise ValueError("参数缺少必要的'rowid'字段")
|
||||
|
||||
current_rowid = item["rowid"]
|
||||
bag_file = bag_by_id.get(current_rowid)
|
||||
if not bag_file:
|
||||
raise ValueError("未找到对应ID的本地记录")
|
||||
|
||||
raw_params = item.get("params")
|
||||
current_param = (
|
||||
item.get("param") if isinstance(item.get("param"), dict) else None
|
||||
)
|
||||
bag_name = item.get("bag_name")
|
||||
if not bag_name and current_param:
|
||||
bag_name = current_param.get("bag_name")
|
||||
if not bag_name and isinstance(raw_params, list) and raw_params:
|
||||
first_param = raw_params[0]
|
||||
if isinstance(first_param, dict):
|
||||
bag_name = first_param.get("bag_name")
|
||||
if not bag_name:
|
||||
bag_name = (
|
||||
merge_name_by_src.get(bag_file.file_name)
|
||||
or bag_file.file_name
|
||||
)
|
||||
if not bag_name:
|
||||
raise ValueError("bag_name为空")
|
||||
|
||||
payloads = []
|
||||
if raw_params is not None:
|
||||
if not isinstance(raw_params, list) or not raw_params:
|
||||
raise ValueError("params必须为非空数组")
|
||||
for param in raw_params:
|
||||
if not isinstance(param, dict):
|
||||
raise ValueError("params元素必须为对象")
|
||||
if not param.get("bag_name"):
|
||||
param["bag_name"] = bag_name
|
||||
payloads.append((param, None))
|
||||
elif current_param:
|
||||
if not current_param.get("bag_name"):
|
||||
current_param["bag_name"] = bag_name
|
||||
payloads.append((current_param, None))
|
||||
else:
|
||||
events = tag_events_by_bag.get(current_rowid, [])
|
||||
if events:
|
||||
for ev in events:
|
||||
payloads.append((_payload_from_event(ev, bag_name), ev))
|
||||
else:
|
||||
payloads.append((_payload_from_bag(bag_file, bag_name), None))
|
||||
except Exception as e:
|
||||
bag_failures.append(
|
||||
{
|
||||
"index": idx,
|
||||
"rowid": item.get("rowid") if isinstance(item, dict) else None,
|
||||
"message": f"处理失败:{str(e)}",
|
||||
"error_type": type(e).__name__,
|
||||
}
|
||||
)
|
||||
result["fail_count"] += 1
|
||||
result["fail_details"].extend(bag_failures)
|
||||
continue
|
||||
|
||||
for ev_idx, (payload, ev) in enumerate(payloads):
|
||||
# print(
|
||||
# f"【insert_rootdb】发送到RootDB: rowid={current_rowid}, "
|
||||
# f"event_index={ev_idx}, event_id={ev.event_id if ev else None}, "
|
||||
# f"payload={payload}"
|
||||
# )
|
||||
try:
|
||||
response = requests.post(
|
||||
url=API_URL,
|
||||
json=[payload],
|
||||
timeout=TIMEOUT,
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
response_data = response.json()
|
||||
if not isinstance(response_data, list) or len(response_data) == 0:
|
||||
raise ValueError("远程API返回格式错误:预期非空列表")
|
||||
if not isinstance(response_data[0], dict) or "success" not in response_data[0]:
|
||||
raise ValueError("远程API返回缺少'success'字段")
|
||||
|
||||
if not response_data[0].get("success"):
|
||||
bag_failures.append(
|
||||
{
|
||||
"index": idx,
|
||||
"rowid": current_rowid,
|
||||
"event_index": ev_idx,
|
||||
"event_id": ev.event_id if ev else None,
|
||||
"message": "远程API插入失败",
|
||||
"remote_response": response_data[0],
|
||||
}
|
||||
)
|
||||
except RequestException as e:
|
||||
bag_failures.append(
|
||||
{
|
||||
"index": idx,
|
||||
"rowid": current_rowid,
|
||||
"event_index": ev_idx,
|
||||
"event_id": ev.event_id if ev else None,
|
||||
"message": f"网络请求失败:{str(e)}",
|
||||
"error_type": type(e).__name__,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
bag_failures.append(
|
||||
{
|
||||
"index": idx,
|
||||
"rowid": current_rowid,
|
||||
"event_index": ev_idx,
|
||||
"event_id": ev.event_id if ev else None,
|
||||
"message": f"处理失败:{str(e)}",
|
||||
"error_type": type(e).__name__,
|
||||
}
|
||||
)
|
||||
|
||||
if bag_failures:
|
||||
result["fail_count"] += 1
|
||||
result["fail_details"].extend(bag_failures)
|
||||
continue
|
||||
|
||||
bag_file.sync_status = SyncStatus.SYNCED
|
||||
bag_file.qa_confirm_time = datetime.now()
|
||||
result["success_count"] += 1
|
||||
|
||||
db.session.commit()
|
||||
return (
|
||||
jsonify({
|
||||
"success": True,
|
||||
"message": f"处理完成,成功{result['success_count']}条,失败{result['fail_count']}条",
|
||||
"result": result,
|
||||
}),
|
||||
200,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({"success": False, "message": f"服务器处理错误:{str(e)}"}), 500
|
||||
0
app/blueprints/data_factory/__inin__.py
Normal file
0
app/blueprints/data_factory/__inin__.py
Normal file
2600
app/blueprints/data_factory/routes.py
Normal file
2600
app/blueprints/data_factory/routes.py
Normal file
File diff suppressed because it is too large
Load Diff
50
app/blueprints/data_factory/services.py
Normal file
50
app/blueprints/data_factory/services.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from datetime import datetime
|
||||
|
||||
from app.models import EventSource, TaggingEvents, VerdictStatus
|
||||
|
||||
def create_tagging_event(db, **kwargs):
|
||||
"""
|
||||
创建并提交TaggingEvents记录
|
||||
参数:
|
||||
db: SQLAlchemy数据库实例
|
||||
kwargs: 字段键值对,支持所有非主键字段
|
||||
|
||||
返回: (success: bool, message: str, event: TaggingEvents)
|
||||
"""
|
||||
# 1. 创建事件对象并设置属性
|
||||
event = TaggingEvents()
|
||||
|
||||
# 2. 动态设置字段值
|
||||
valid_fields = {
|
||||
'bag_id', 'source', 'rule_version_id', 'reviewer_id', 'verdict',
|
||||
'level1_tag_id', 'level2_tag_id', 'level3_tag_id', 'level4_tag_id'
|
||||
}
|
||||
|
||||
for field, value in kwargs.items():
|
||||
if field in valid_fields:
|
||||
setattr(event, field, value)
|
||||
# 自动处理特殊字段类型[1,4](@ref)
|
||||
elif field == 'ts_event' and isinstance(value, datetime):
|
||||
event.ts_event = value
|
||||
|
||||
# 3. 枚举字段验证[1](@ref)
|
||||
if 'source' in kwargs and not isinstance(kwargs['source'], EventSource):
|
||||
return False, "source必须是EventSource枚举类型", None
|
||||
|
||||
if 'verdict' in kwargs and not isinstance(kwargs['verdict'], VerdictStatus):
|
||||
return False, "verdict必须是VerdictStatus枚举类型", None
|
||||
|
||||
# 4. 提交到数据库[2,4](@ref)
|
||||
try:
|
||||
db.session.add(event)
|
||||
db.session.commit()
|
||||
return True, "记录创建成功", event
|
||||
except SQLAlchemyError as e:
|
||||
db.session.rollback()
|
||||
# app.logger.error(f"数据库写入失败: {str(e)}")
|
||||
return False, f"数据库错误: {str(e)}", None
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
# app.logger.error(f"未知错误: {str(e)}")
|
||||
return False, f"系统错误: {str(e)}", None
|
||||
0
app/blueprints/label_data/__inin__.py
Normal file
0
app/blueprints/label_data/__inin__.py
Normal file
294
app/blueprints/label_data/rotutes.py
Normal file
294
app/blueprints/label_data/rotutes.py
Normal file
@@ -0,0 +1,294 @@
|
||||
from datetime import datetime
|
||||
from flask import Blueprint, current_app, request, jsonify
|
||||
from flask_jwt_extended import jwt_required
|
||||
import requests
|
||||
from sqlalchemy import func
|
||||
from app import db
|
||||
from app.models import Fst
|
||||
from app.utils.fst_tree import build_tree
|
||||
|
||||
|
||||
label_bp = Blueprint('label', __name__)
|
||||
|
||||
|
||||
def format_fst_item(fst_item):
|
||||
"""将Fst对象转换为指定格式的字典"""
|
||||
# 从reserved_json中获取type,如果没有则默认为空字符串
|
||||
item_type = ""
|
||||
if fst_item.reserved_json and "type" in fst_item.reserved_json:
|
||||
item_type = fst_item.reserved_json["type"]
|
||||
|
||||
return {
|
||||
"date": fst_item.update_time.strftime('%Y-%m-%d'), # 格式化日期
|
||||
"id": fst_item.id,
|
||||
"name": fst_item.name,
|
||||
"parentId": None if fst_item.parent_id==591 else fst_item.parent_id, # 注意这里是驼峰命名
|
||||
"name_cn": fst_item.name_cn,
|
||||
"annotation":fst_item.annotation,
|
||||
"level":fst_item.level
|
||||
}
|
||||
|
||||
@label_bp.route('/fst-tree', methods=['GET'])
|
||||
def get_fst_tree():
|
||||
"""获取所有FST数据并格式化为树形结构列表"""
|
||||
try:
|
||||
# 查询所有FST记录
|
||||
all_fst = Fst.query.all()
|
||||
|
||||
# 转换为指定格式
|
||||
formatted_data = [format_fst_item(item) for item in all_fst]
|
||||
|
||||
# 返回JSON响应
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"data": formatted_data
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}), 500
|
||||
|
||||
|
||||
@label_bp.route('/create-levelone', methods=['POST'])
|
||||
@jwt_required()
|
||||
def create_levelone():
|
||||
# 获取请求数据
|
||||
data = request.get_json()
|
||||
|
||||
# 验证必要参数
|
||||
required_fields = ['name', 'level', 'annotation']
|
||||
for field in required_fields:
|
||||
if field not in data:
|
||||
return jsonify({'error': f'Missing required field: {field}'}), 400
|
||||
|
||||
# 验证层级是否为1
|
||||
if data['level'] != 1:
|
||||
return jsonify({'error': 'Level must be 1 for first-level tags'}), 400
|
||||
|
||||
|
||||
try:
|
||||
# 查询当前最大的id值
|
||||
max_id_result = db.session.query(func.max(Fst.id)).first()
|
||||
max_id = max_id_result[0] if max_id_result[0] is not None else 0
|
||||
|
||||
# 新id为最大id + 1
|
||||
new_id = max_id + 1
|
||||
|
||||
# 创建新的一级标签
|
||||
new_tag = Fst(
|
||||
id=new_id,
|
||||
name=data['name'],
|
||||
level=data['level'],
|
||||
annotation=data['annotation'],
|
||||
parent_id=591, # 固定设置parent_id为591
|
||||
update_time=datetime.now() # 设置更新时间
|
||||
# 其他字段如name_cn、reserved_json、bag_sum如果有默认值可以不用指定
|
||||
)
|
||||
|
||||
# 添加到数据库会话并提交
|
||||
db.session.add(new_tag)
|
||||
db.session.commit()
|
||||
|
||||
# print(f"新标签的ID: {new_tag.id}")
|
||||
# 返回创建成功的标签信息
|
||||
return jsonify({
|
||||
'message': 'First-level tag created successfully',
|
||||
'tag': {
|
||||
'id': new_tag.id,
|
||||
'name': new_tag.name,
|
||||
'level': new_tag.level,
|
||||
'annotation': new_tag.annotation,
|
||||
'parent_id': new_tag.parent_id,
|
||||
'update_time': new_tag.update_time.isoformat()
|
||||
}
|
||||
}), 201
|
||||
|
||||
except Exception as e:
|
||||
# 发生错误时回滚
|
||||
db.session.rollback()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@label_bp.route('/update-fst-annotation', methods=['POST'])
|
||||
@jwt_required()
|
||||
def update_fst_annotation():
|
||||
# 获取请求数据
|
||||
data = request.get_json()
|
||||
|
||||
# 验证必要参数
|
||||
required_fields = ['name', 'level', 'annotation']
|
||||
for field in required_fields:
|
||||
if field not in data:
|
||||
return jsonify({'error': f'Missing required field: {field}'}), 400
|
||||
|
||||
try:
|
||||
# 根据name和level查询对应的记录
|
||||
fst_record = Fst.query.filter_by(
|
||||
name=data['name'],
|
||||
level=data['level']
|
||||
).first()
|
||||
|
||||
# 检查记录是否存在
|
||||
if not fst_record:
|
||||
return jsonify({
|
||||
'error': f'No record found with name: {data["name"]} and level: {data["level"]}'
|
||||
}), 404
|
||||
|
||||
# 更新annotation字段和更新时间
|
||||
fst_record.annotation = data['annotation']
|
||||
fst_record.update_time = datetime.now() # 更新时间戳
|
||||
|
||||
# 提交修改
|
||||
db.session.commit()
|
||||
|
||||
# 返回更新成功的信息
|
||||
return jsonify({
|
||||
'message': 'Annotation updated successfully',
|
||||
'updated_record': {
|
||||
'id': fst_record.id,
|
||||
'name': fst_record.name,
|
||||
'level': fst_record.level,
|
||||
'annotation': fst_record.annotation,
|
||||
'update_time': fst_record.update_time.isoformat()
|
||||
}
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
# 发生错误时回滚
|
||||
db.session.rollback()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@label_bp.route('/add-fst', methods=['POST'])
|
||||
@jwt_required()
|
||||
def add_fst():
|
||||
# 获取请求数据
|
||||
data = request.get_json()
|
||||
|
||||
# 验证必要参数
|
||||
required_fields = ['name', 'level', 'parentName', 'annotation']
|
||||
for field in required_fields:
|
||||
if field not in data:
|
||||
return jsonify({'error': f'Missing required field: {field}'}), 400
|
||||
|
||||
try:
|
||||
# 根据parentName查询父标签的记录,获取parent_id
|
||||
parent_record = Fst.query.filter_by(name=data['parentName']).first()
|
||||
|
||||
# 检查父标签是否存在
|
||||
if not parent_record:
|
||||
return jsonify({
|
||||
'error': f'Parent tag not found with name: {data["parentName"]}'
|
||||
}), 404
|
||||
|
||||
# 查询当前最大的id值
|
||||
max_id_result = db.session.query(func.max(Fst.id)).first()
|
||||
max_id = max_id_result[0] if max_id_result[0] is not None else 0
|
||||
|
||||
# 新id为最大id + 1
|
||||
new_id = max_id + 1
|
||||
|
||||
# 创建新的fst记录
|
||||
new_fst = Fst(
|
||||
id=new_id,
|
||||
name=data['name'],
|
||||
level=data['level'],
|
||||
parent_id=parent_record.id, # 使用查询到的父标签ID
|
||||
annotation=data['annotation'],
|
||||
update_time=datetime.now()
|
||||
)
|
||||
|
||||
# 添加到数据库并提交
|
||||
db.session.add(new_fst)
|
||||
db.session.commit()
|
||||
|
||||
# 返回成功信息
|
||||
return jsonify({
|
||||
'message': 'Fst record created successfully',
|
||||
'record': {
|
||||
'id': new_fst.id,
|
||||
'name': new_fst.name,
|
||||
'level': new_fst.level,
|
||||
'parent_id': new_fst.parent_id,
|
||||
'parentName': data['parentName'],
|
||||
'annotation': new_fst.annotation,
|
||||
'update_time': new_fst.update_time.isoformat()
|
||||
}
|
||||
}), 201
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
|
||||
@label_bp.route("/sync-fst", methods=["POST"])
|
||||
def sync_fst():
|
||||
# 1. 解析请求参数
|
||||
params = request.get_json() or {}
|
||||
print(f"【请求参数】原始参数: {params}")
|
||||
|
||||
name = params.get("name", "")
|
||||
parent_name = params.get("parent_name", "")
|
||||
|
||||
# 参数校验
|
||||
if not name or not parent_name:
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"success": False,
|
||||
"message": "缺少必要参数(name 或 parent_name)",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
# 2. 转发请求配置
|
||||
API_URL = "http://10.0.240.4:5232/api/fst/update"
|
||||
TIMEOUT = 10 # 超时时间(秒)
|
||||
data = {"name": name, "parent_name": parent_name}
|
||||
|
||||
try:
|
||||
# 3. 发送转发请求
|
||||
res = requests.post(url=API_URL, json=data, timeout=TIMEOUT)
|
||||
res.raise_for_status() # 自动抛出 4xx/5xx 状态码的异常
|
||||
|
||||
# 4. 处理对方 API 的响应(假设返回 JSON 格式)
|
||||
try:
|
||||
remote_response = res.json()
|
||||
except ValueError:
|
||||
# 对方返回非 JSON 格式响应
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"success": False,
|
||||
"message": "同步失败:目标接口返回无效格式",
|
||||
"details": res.text,
|
||||
}
|
||||
),
|
||||
500,
|
||||
)
|
||||
|
||||
# 5. 返回成功响应
|
||||
return jsonify(
|
||||
{
|
||||
"success": True,
|
||||
"message": "同步成功",
|
||||
"data": remote_response, # 携带对方 API 的返回数据
|
||||
}
|
||||
)
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
# 捕获所有 requests 相关异常(超时、连接失败、4xx/5xx 等)
|
||||
error_msg = f"同步失败:{str(e)}"
|
||||
print(f"【转发请求错误】{error_msg}")
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"success": False,
|
||||
"message": error_msg,
|
||||
}
|
||||
),
|
||||
500,
|
||||
)
|
||||
0
app/blueprints/remote_data/__init__.py
Normal file
0
app/blueprints/remote_data/__init__.py
Normal file
539
app/blueprints/remote_data/rotutes.py
Normal file
539
app/blueprints/remote_data/rotutes.py
Normal file
@@ -0,0 +1,539 @@
|
||||
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)}"})
|
||||
|
||||
|
||||
|
||||
5
app/blueprints/vlm/__init__.py
Normal file
5
app/blueprints/vlm/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# from flask import Blueprint
|
||||
|
||||
# auth_bp = Blueprint('auth', __name__)
|
||||
|
||||
# from . import routes
|
||||
885
app/blueprints/vlm/rotutes.py
Normal file
885
app/blueprints/vlm/rotutes.py
Normal file
@@ -0,0 +1,885 @@
|
||||
from datetime import datetime
|
||||
from functools import lru_cache
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import uuid
|
||||
from flask import Blueprint, current_app, json, request, jsonify
|
||||
from flask_jwt_extended import get_jwt_identity, jwt_required
|
||||
import requests
|
||||
from sqlalchemy import and_, func
|
||||
from app import db
|
||||
from app.models import BagFile, BagStatus, Fst, User, VlmFilter
|
||||
from app import create_app
|
||||
from app.utils.get_vlm_token import Demo
|
||||
from app.utils.driving_tree import vlm_data
|
||||
|
||||
vlm_bp = Blueprint("vlm", __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
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def get_cached_token():
|
||||
TOKEN_EXPIRE_SECONDS = 7200
|
||||
"""获取缓存的Token,如果过期则重新获取(固定有效期)"""
|
||||
# 获取Token时记录当前时间(作为获取时间)
|
||||
token_info = Demo().getAuthToken()
|
||||
token_info["fetch_time"] = time.time() # 手动添加获取时间
|
||||
|
||||
# 检查是否过期(当前时间 - 获取时间 > 有效期)
|
||||
if time.time() - token_info["fetch_time"] >= TOKEN_EXPIRE_SECONDS - 60:
|
||||
# 提前60秒刷新
|
||||
get_cached_token.cache_clear()
|
||||
new_token = Demo().getAuthToken()
|
||||
new_token["fetch_time"] = time.time() # 记录新的获取时间
|
||||
return new_token
|
||||
return token_info
|
||||
|
||||
|
||||
@vlm_bp.route("/insert-csv", methods=["POST"])
|
||||
@jwt_required()
|
||||
def insert_db_by_csv():
|
||||
try:
|
||||
# 1. 获取上传的CSV文件
|
||||
if "file" not in request.files:
|
||||
return jsonify({"code": 400, "message": "未上传CSV文件"}), 400
|
||||
|
||||
file = request.files["file"]
|
||||
if file.filename == "":
|
||||
return jsonify({"code": 400, "message": "未选择文件"}), 400
|
||||
|
||||
# 验证文件类型
|
||||
if not file.filename.endswith(".csv"):
|
||||
return jsonify({"code": 400, "message": "仅支持CSV格式文件"}), 400
|
||||
|
||||
# 2. 直接从form中获取表单字段(无需解析JSON)
|
||||
# 验证必要的表单参数
|
||||
required_fields = ["level1Tag", "status"]
|
||||
form_params = {}
|
||||
for field in required_fields:
|
||||
value = request.form.get(field)
|
||||
if not value:
|
||||
return (
|
||||
jsonify({"code": 400, "message": f"缺少必要的表单参数: {field}"}),
|
||||
400,
|
||||
)
|
||||
form_params[field] = value
|
||||
|
||||
# 额外获取可选的表单字段
|
||||
form_params["radioGroup"] = request.form.get("radioGroup", "")
|
||||
form_params["fileKey"] = request.form.get("fileKey", "")
|
||||
|
||||
# 转换level1Tag为整数
|
||||
try:
|
||||
level1_tag = int(form_params["level1Tag"])
|
||||
except ValueError:
|
||||
return jsonify({"code": 400, "message": "level1Tag必须是整数"}), 400
|
||||
|
||||
# 验证status是否有效
|
||||
status_str = form_params["status"]
|
||||
try:
|
||||
status = BagStatus[status_str] # 假设使用枚举类
|
||||
except KeyError:
|
||||
return (
|
||||
jsonify({"code": 400, "message": f"无效的status值: {status_str}"}),
|
||||
400,
|
||||
)
|
||||
|
||||
# 替换bag_status
|
||||
if form_params["radioGroup"] == "annotation":
|
||||
bag_status = 0
|
||||
if form_params["radioGroup"] == "qa":
|
||||
bag_status = 1
|
||||
|
||||
# 插入fst_version字段
|
||||
# 获取当前时间
|
||||
current_time = datetime.now()
|
||||
|
||||
# 格式化年月日时,用下划线连接
|
||||
formatted_time = f"{current_time.year}-{current_time.month:02d}-{current_time.day:02d}-{current_time.hour:02d}"
|
||||
fst_version = "vlm_" + get_jwt_identity() + "_" + formatted_time
|
||||
|
||||
# 3. 解析CSV文件内容
|
||||
import csv
|
||||
from io import StringIO
|
||||
|
||||
# from datetime import datetime
|
||||
|
||||
# 读取CSV内容
|
||||
csv_content = file.stream.read().decode("utf-8")
|
||||
csv_file = StringIO(csv_content)
|
||||
csv_reader = csv.DictReader(csv_file)
|
||||
|
||||
# 验证CSV表头
|
||||
if "bag_name" not in csv_reader.fieldnames:
|
||||
return (
|
||||
jsonify({"code": 400, "message": "CSV文件缺少必要的'bag_name'列"}),
|
||||
400,
|
||||
)
|
||||
|
||||
# 4. 组合CSV数据与表单数据,插入数据库
|
||||
inserted_count = 0
|
||||
skipped_count = 0
|
||||
error_records = []
|
||||
|
||||
for row_num, row in enumerate(
|
||||
csv_reader, start=2
|
||||
): # 行号从2开始(表头为第1行)
|
||||
bag_name = row.get("bag_name", "").strip()
|
||||
|
||||
if not bag_name:
|
||||
skipped_count += 1
|
||||
error_records.append(
|
||||
{"row": row_num, "reason": "bag_name为空", "content": row}
|
||||
)
|
||||
continue
|
||||
|
||||
# 从文件名提取时间和视频URL(复用你的函数)
|
||||
datetime_full, video_url = extract_datetime_from_filename(bag_name)
|
||||
|
||||
# 组合数据:CSV的bag_name + 表单的level1Tag、status等
|
||||
new_bag = BagFile(
|
||||
file_name=bag_name,
|
||||
level1_tag_id=level1_tag, # 表单字段
|
||||
status=status, # 表单字段
|
||||
capture_datetime=datetime_full,
|
||||
create_time=datetime.now(),
|
||||
update_time=datetime.now(),
|
||||
bag_status=bag_status,
|
||||
sync_status="SYNC_NOT_READY",
|
||||
user_id=get_jwt_identity(), # 当前用户ID
|
||||
video_url=video_url,
|
||||
fst_version=fst_version,
|
||||
)
|
||||
|
||||
db.session.add(new_bag)
|
||||
inserted_count += 1
|
||||
|
||||
# 提交事务
|
||||
db.session.commit()
|
||||
|
||||
# 5. 返回处理结果
|
||||
return jsonify(
|
||||
{
|
||||
"code": 200,
|
||||
"message": f"处理完成,成功插入 {inserted_count} 条记录,跳过 {skipped_count} 条记录",
|
||||
"data": {
|
||||
"inserted_count": inserted_count,
|
||||
"skipped_count": skipped_count,
|
||||
"errors": error_records if skipped_count > 0 else None,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"【上传CSV接口异常】: {str(e)}")
|
||||
return jsonify({"code": 500, "message": f"处理失败:{str(e)}"}), 500
|
||||
|
||||
|
||||
@vlm_bp.route("/get-models", methods=["GET"])
|
||||
def get_models():
|
||||
result = {
|
||||
"code": 200,
|
||||
"data": [
|
||||
{
|
||||
"id": 3,
|
||||
"name": "图片模型Base",
|
||||
"url": "http://10.0.220.110:20080",
|
||||
"type": 0,
|
||||
"remark": "没有finetune的初始模型\nhttp://10.0.220.110:20080",
|
||||
"createUserId": 3,
|
||||
"status": 1,
|
||||
"createdAt": "2024-11-21T07:36:34.337Z",
|
||||
"updatedAt": "2025-04-10T08:00:03.727Z",
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "图片模型FT4",
|
||||
"url": "http://10.0.220.226:20081",
|
||||
"type": 0,
|
||||
"remark": "第4轮微调模型\nhttp://10.0.220.226:20081",
|
||||
"createUserId": 3,
|
||||
"status": 1,
|
||||
"createdAt": "2025-04-25T07:22:06.986Z",
|
||||
"updatedAt": "2025-05-09T07:00:22.667Z",
|
||||
},
|
||||
],
|
||||
"success": True,
|
||||
"message": "success",
|
||||
}
|
||||
# token = get_cached_token()
|
||||
# API_URL = "http://10.0.220.110/api/app/model/models-server?modelType=0"
|
||||
# headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||||
# response = requests.get(url=API_URL, headers=headers)
|
||||
# result = response.json()
|
||||
return jsonify({"code": 200, "data": result})
|
||||
|
||||
|
||||
@vlm_bp.route("/get-datasets", methods=["GET"])
|
||||
def get_datasets():
|
||||
# result = {
|
||||
# "code": 200,
|
||||
# "data": [
|
||||
# {
|
||||
# "id": 99,
|
||||
# "name": "Momenta",
|
||||
# "path": None,
|
||||
# "type": 0,
|
||||
# "status": 3,
|
||||
# "remark": "这是数据集",
|
||||
# "index_name": "0_33f0ba5e-d00d-4033-b2d4-3a491a347f25",
|
||||
# "create_user_id": None,
|
||||
# "is_delete": 0,
|
||||
# "createdAt": "2025-04-25T09:10:33.072Z",
|
||||
# "updatedAt": "2025-05-19T15:03:33.366Z",
|
||||
# "file_count": None,
|
||||
# },
|
||||
# {
|
||||
# "id": 107,
|
||||
# "name": "MB",
|
||||
# "path": None,
|
||||
# "type": 0,
|
||||
# "status": 3,
|
||||
# "remark": "这是数据集",
|
||||
# "index_name": "0_abcb005e-6782-4b91-877e-4b1cebcf52e7",
|
||||
# "create_user_id": None,
|
||||
# "is_delete": 0,
|
||||
# "createdAt": "2025-05-15T09:49:23.056Z",
|
||||
# "updatedAt": "2025-05-19T11:35:21.063Z",
|
||||
# "file_count": None,
|
||||
# },
|
||||
# {
|
||||
# "id": 124,
|
||||
# "name": "MB_DC_2508",
|
||||
# "path": None,
|
||||
# "type": 0,
|
||||
# "status": 3,
|
||||
# "remark": "这是数据集",
|
||||
# "index_name": "0_35caf344-a572-40d1-bc31-e426b785eaa2",
|
||||
# "create_user_id": None,
|
||||
# "is_delete": 0,
|
||||
# "createdAt": "2025-08-29T01:30:34.199Z",
|
||||
# "updatedAt": "2025-09-12T07:45:58.658Z",
|
||||
# "file_count": None,
|
||||
# },
|
||||
# {
|
||||
# "id": 125,
|
||||
# "name": "MB_DC_2504_06",
|
||||
# "path": None,
|
||||
# "type": 0,
|
||||
# "status": 3,
|
||||
# "remark": "这是数据集",
|
||||
# "index_name": "0_46805e78-738e-439d-9b65-2b5a9cdd5ee5",
|
||||
# "create_user_id": None,
|
||||
# "is_delete": 0,
|
||||
# "createdAt": "2025-09-04T09:54:12.411Z",
|
||||
# "updatedAt": "2025-09-11T19:37:03.399Z",
|
||||
# "file_count": None,
|
||||
# },
|
||||
# {
|
||||
# "id": 126,
|
||||
# "name": "MB_DC_2507",
|
||||
# "path": None,
|
||||
# "type": 0,
|
||||
# "status": 3,
|
||||
# "remark": "这是数据集",
|
||||
# "index_name": "0_14b37c68-cf81-4c5b-b232-43323c7501e7",
|
||||
# "create_user_id": None,
|
||||
# "is_delete": 0,
|
||||
# "createdAt": "2025-09-11T01:47:57.010Z",
|
||||
# "updatedAt": "2025-09-12T03:45:47.002Z",
|
||||
# "file_count": None,
|
||||
# },
|
||||
# ],
|
||||
# "success": True,
|
||||
# "message": "success",
|
||||
# }
|
||||
token = get_cached_token()
|
||||
API_URL = "http://10.0.220.110/api/app/model/datasets-list?modelId=4"
|
||||
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
||||
response = requests.get(url=API_URL, headers=headers)
|
||||
result = response.json()
|
||||
return jsonify({"code": 200, "data": result})
|
||||
|
||||
|
||||
@vlm_bp.route("/get-search", methods=["POST"])
|
||||
def get_search_list():
|
||||
# result = vlm_data
|
||||
API_URL = "http://10.0.220.110/api/app/search/4"
|
||||
data = request.json
|
||||
result = requests.post(url=API_URL, json=data)
|
||||
result=result.json()
|
||||
return jsonify({"code": 200, "data": result})
|
||||
|
||||
|
||||
@vlm_bp.route("/get-alltags", methods=["GET"])
|
||||
def get_alltags():
|
||||
try:
|
||||
# 查询所有标签
|
||||
all_tags = Fst.query.all()
|
||||
|
||||
# 转换为所需的结构
|
||||
result = []
|
||||
for tag in all_tags:
|
||||
tag_data = {
|
||||
"id": tag.id,
|
||||
"name": tag.name,
|
||||
"level": tag.level,
|
||||
"parent_id": tag.parent_id,
|
||||
}
|
||||
result.append(tag_data)
|
||||
|
||||
# 返回JSON响应
|
||||
return jsonify(result), 200
|
||||
|
||||
except Exception as e:
|
||||
# 错误处理
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@vlm_bp.route("/insert-vlm-filter", methods=["POST"])
|
||||
@jwt_required()
|
||||
def add_vlm_filter():
|
||||
"""
|
||||
新增VlmFilter记录
|
||||
请求参数:
|
||||
{
|
||||
"level1": {
|
||||
"id": 407,
|
||||
"name": "TOLL_STATION",
|
||||
"level": 1
|
||||
},
|
||||
"level2": {
|
||||
"id": 616,
|
||||
"name": "TOLL_STATION_LEAVE",
|
||||
"level": 2
|
||||
},
|
||||
"level3": {
|
||||
"id": 579,
|
||||
"name": "TOLL_STATION_LEAVE_PASSING_NO_LANE_MARKING_SQUARE_AFTER_ROD",
|
||||
"level": 3
|
||||
},
|
||||
"level4": null,
|
||||
"bagname": "PL162802_event_hmi_console_event_20250827-182132_0.bag",
|
||||
"comment": "12345",
|
||||
"status": 0,
|
||||
"video_url":"",
|
||||
}
|
||||
"""
|
||||
try:
|
||||
# 1. 获取当前用户ID(从JWT中提取)
|
||||
current_user_id = get_jwt_identity()
|
||||
|
||||
# 2. 解析请求参数
|
||||
params = request.json
|
||||
|
||||
# 3. 验证必要参数
|
||||
required_fields = ["level1", "level2", "level3", "bagname", "status"]
|
||||
if not all(key in params for key in required_fields):
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"code": 400,
|
||||
"message": f"缺少必要参数: {', '.join(required_fields)}",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
# 4. 提取参数值
|
||||
level1 = params["level1"]
|
||||
level2 = params["level2"]
|
||||
level3 = params["level3"]
|
||||
level4 = params.get("level4")
|
||||
bagname = params["bagname"]
|
||||
comment = params.get("comment", "")
|
||||
status = params["status"]
|
||||
video_url = params["video_url"]
|
||||
|
||||
# 5. 提取各级标签ID
|
||||
level1_tag_id = level1.get("id") if level1 else None
|
||||
level2_tag_id = level2.get("id") if level2 else None
|
||||
level3_tag_id = level3.get("id") if level3 else None
|
||||
level4_tag_id = level4.get("id") if level4 else None
|
||||
|
||||
# 6. 验证标签层级关系和存在性
|
||||
# 验证一级标签
|
||||
if level1_tag_id is not None:
|
||||
level1_tag = Fst.query.get(level1_tag_id)
|
||||
if not level1_tag:
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"code": 400,
|
||||
"message": f"一级标签不存在(ID: {level1_tag_id})",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
# 验证二级标签
|
||||
if level2_tag_id is not None:
|
||||
level2_tag = Fst.query.get(level2_tag_id)
|
||||
if not level2_tag:
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"code": 400,
|
||||
"message": f"二级标签不存在(ID: {level2_tag_id})",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
if not level1_tag_id:
|
||||
return (
|
||||
jsonify(
|
||||
{"code": 400, "message": "二级标签存在时,一级标签不能为空"}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
if level2_tag.parent_id != level1_tag_id:
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"code": 400,
|
||||
"message": f"二级标签的父级ID({level2_tag.parent_id})与一级标签ID({level1_tag_id})不匹配",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
# 验证三级标签
|
||||
if level3_tag_id is not None:
|
||||
level3_tag = Fst.query.get(level3_tag_id)
|
||||
if not level3_tag:
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"code": 400,
|
||||
"message": f"三级标签不存在(ID: {level3_tag_id})",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
if not level2_tag_id:
|
||||
return (
|
||||
jsonify(
|
||||
{"code": 400, "message": "三级标签存在时,二级标签不能为空"}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
if level3_tag.parent_id != level2_tag_id:
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"code": 400,
|
||||
"message": f"三级标签的父级ID({level3_tag.parent_id})与二级标签ID({level2_tag_id})不匹配",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
# 验证四级标签
|
||||
if level4_tag_id is not None:
|
||||
level4_tag = Fst.query.get(level4_tag_id)
|
||||
if not level4_tag:
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"code": 400,
|
||||
"message": f"四级标签不存在(ID: {level4_tag_id})",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
if not level3_tag_id:
|
||||
return (
|
||||
jsonify(
|
||||
{"code": 400, "message": "四级标签存在时,三级标签不能为空"}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
if level4_tag.parent_id != level3_tag_id:
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"code": 400,
|
||||
"message": f"四级标签的父级ID({level4_tag.parent_id})与三级标签ID({level3_tag_id})不匹配",
|
||||
}
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
# 7. 创建新的VlmFilter记录
|
||||
new_vlm = VlmFilter(
|
||||
bag_name=bagname,
|
||||
level1_tag_id=level1_tag_id,
|
||||
level2_tag_id=level2_tag_id,
|
||||
level3_tag_id=level3_tag_id,
|
||||
level4_tag_id=level4_tag_id,
|
||||
comment=comment,
|
||||
user_id=current_user_id,
|
||||
# collection_time=datetime.now(),
|
||||
create_time=datetime.now(),
|
||||
status=status,
|
||||
video_url=video_url,
|
||||
)
|
||||
|
||||
# 8. 添加到数据库并提交
|
||||
db.session.add(new_vlm)
|
||||
db.session.commit()
|
||||
|
||||
# 9. 返回成功响应
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"code": 200,
|
||||
"message": "数据插入成功",
|
||||
"data": {
|
||||
"id": new_vlm.id,
|
||||
"bag_name": new_vlm.bag_name,
|
||||
"level1_tag_id": new_vlm.level1_tag_id,
|
||||
"level2_tag_id": new_vlm.level2_tag_id,
|
||||
"level3_tag_id": new_vlm.level3_tag_id,
|
||||
"level4_tag_id": new_vlm.level4_tag_id,
|
||||
"user_id": new_vlm.user_id,
|
||||
"create_time": new_vlm.create_time.strftime(
|
||||
"%Y-%m-%d %H:%M:%S"
|
||||
),
|
||||
"status": new_vlm.status,
|
||||
},
|
||||
}
|
||||
),
|
||||
200,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({"code": 500, "message": f"服务器错误: {str(e)}"}), 500
|
||||
|
||||
|
||||
@vlm_bp.route("/get-vlm-filter-list", methods=["POST"])
|
||||
@jwt_required()
|
||||
def get_vlm_filter_list():
|
||||
try:
|
||||
# 1. 解析请求参数
|
||||
params = request.get_json() or {}
|
||||
|
||||
# 提取分页参数,设置默认值
|
||||
page = params.get("page", 1)
|
||||
per_page = params.get("per_page", 20)
|
||||
|
||||
# 提取过滤参数
|
||||
bag_name = params.get("bag_name", "").strip()
|
||||
level1_tag = params.get("level1_tag", "")
|
||||
start_datetime = params.get("start_datetime", "").strip()
|
||||
end_datetime = params.get("end_datetime", "").strip()
|
||||
user_id = params.get("user_id")
|
||||
|
||||
# 2. 构建基础查询
|
||||
query = VlmFilter.query
|
||||
|
||||
# 3. 动态添加过滤条件
|
||||
conditions = []
|
||||
|
||||
# 固定条件:只展示status=0的数据(核心新增)
|
||||
conditions.append(VlmFilter.status == 0)
|
||||
|
||||
# 袋名模糊查询
|
||||
if bag_name:
|
||||
conditions.append(VlmFilter.bag_name.like(f"%{bag_name}%"))
|
||||
|
||||
# 一级标签查询
|
||||
if level1_tag:
|
||||
try:
|
||||
level1_id = int(level1_tag)
|
||||
conditions.append(VlmFilter.level1_tag_id == level1_id)
|
||||
except ValueError:
|
||||
print(f"【过滤条件】level1_tag转换失败(非整数): {level1_tag}")
|
||||
|
||||
# 用户ID过滤
|
||||
if user_id:
|
||||
try:
|
||||
user_id_int = int(user_id)
|
||||
conditions.append(VlmFilter.user_id == user_id_int)
|
||||
except ValueError:
|
||||
print(f"【过滤条件】user_id转换失败(非整数): {user_id}")
|
||||
|
||||
# 时间范围查询(按create_time)
|
||||
if start_datetime:
|
||||
try:
|
||||
start_dt = datetime.fromisoformat(start_datetime)
|
||||
conditions.append(VlmFilter.create_time >= start_dt)
|
||||
except ValueError:
|
||||
print(f"【过滤条件】开始时间格式错误: {start_datetime}")
|
||||
|
||||
if end_datetime:
|
||||
try:
|
||||
end_dt = datetime.fromisoformat(end_datetime)
|
||||
conditions.append(VlmFilter.create_time <= end_dt)
|
||||
except ValueError:
|
||||
print(f"【过滤条件】结束时间格式错误: {end_datetime}")
|
||||
|
||||
# 应用所有条件(包含固定的status=0)
|
||||
if conditions:
|
||||
query = query.filter(and_(*conditions))
|
||||
|
||||
# 4. 排序:按create_time降序
|
||||
query = query.order_by(VlmFilter.create_time.desc())
|
||||
|
||||
# 5. 分页查询
|
||||
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
total_items = pagination.total
|
||||
total_pages = pagination.pages
|
||||
|
||||
# 6. 序列化结果
|
||||
result_list = []
|
||||
for item in pagination.items:
|
||||
# 获取标签名称
|
||||
level1_name = (
|
||||
Fst.query.get(item.level1_tag_id).name if item.level1_tag_id else None
|
||||
)
|
||||
level2_name = (
|
||||
Fst.query.get(item.level2_tag_id).name if item.level2_tag_id else None
|
||||
)
|
||||
level3_name = (
|
||||
Fst.query.get(item.level3_tag_id).name if item.level3_tag_id else None
|
||||
)
|
||||
level4_name = (
|
||||
Fst.query.get(item.level4_tag_id).name if item.level4_tag_id else None
|
||||
)
|
||||
|
||||
# 获取用户名
|
||||
username = User.query.get(item.user_id).username if item.user_id else None
|
||||
|
||||
result_list.append(
|
||||
{
|
||||
"id": item.id,
|
||||
"bag_name": item.bag_name,
|
||||
"level1_tag_id": item.level1_tag_id,
|
||||
"level1_tag_name": level1_name,
|
||||
"level2_tag_id": item.level2_tag_id,
|
||||
"level2_tag_name": level2_name,
|
||||
"level3_tag_id": item.level3_tag_id,
|
||||
"level3_tag_name": level3_name,
|
||||
"level4_tag_id": item.level4_tag_id,
|
||||
"level4_tag_name": level4_name,
|
||||
"video_url": item.video_url,
|
||||
"comment": item.comment,
|
||||
"user_id": item.user_id,
|
||||
"username": username,
|
||||
"collection_time": (
|
||||
item.collection_time.isoformat()
|
||||
if item.collection_time
|
||||
else None
|
||||
),
|
||||
"create_time": (
|
||||
item.create_time.isoformat() if item.create_time else None
|
||||
),
|
||||
"init_label": item.init_label,
|
||||
"status": item.status, # 此处返回的status始终为0
|
||||
}
|
||||
)
|
||||
|
||||
# 7. 返回响应
|
||||
return jsonify(
|
||||
{
|
||||
"code": 200,
|
||||
"data": result_list,
|
||||
"total": total_items,
|
||||
"page": page,
|
||||
"pages": total_pages,
|
||||
"message": "查询成功(仅返回status=0的数据)",
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"【接口异常】: {str(e)}")
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
"code": 500,
|
||||
"data": [],
|
||||
"total": 0,
|
||||
"page": page if "page" in locals() else 1,
|
||||
"pages": 0,
|
||||
"message": f"查询出错:{str(e)}",
|
||||
}
|
||||
),
|
||||
500,
|
||||
)
|
||||
|
||||
|
||||
@vlm_bp.route("/send-loacldb", methods=["POST"])
|
||||
@jwt_required()
|
||||
def send_filter_vlm_localdb():
|
||||
try:
|
||||
# 1. 解析请求参数
|
||||
params = request.get_json() or {}
|
||||
|
||||
# 验证必要参数
|
||||
if "data" not in params or not isinstance(params["data"], list):
|
||||
return (
|
||||
jsonify({"code": 400, "message": "缺少必要参数data或data不是数组"}),
|
||||
400,
|
||||
)
|
||||
|
||||
if "add_status" not in params:
|
||||
return jsonify({"code": 400, "message": "缺少必要参数add_status"}), 400
|
||||
|
||||
# 提取参数
|
||||
data_list = params["data"]
|
||||
add_status = params["add_status"]
|
||||
label_status = params["label_status"]
|
||||
|
||||
# 验证add_status是否为整数
|
||||
try:
|
||||
add_status = int(add_status)
|
||||
except ValueError:
|
||||
return jsonify({"code": 400, "message": "add_status必须是整数"}), 400
|
||||
|
||||
# 初始化统计变量
|
||||
inserted_count = 0
|
||||
updated_count = 0
|
||||
error_records = []
|
||||
|
||||
# 获取当前用户ID
|
||||
current_user_id = get_jwt_identity()
|
||||
current_time = datetime.now()
|
||||
# 获取当前时间
|
||||
current_time = datetime.now()
|
||||
# 格式化年月日时,用下划线连接
|
||||
formatted_time = f"{current_time.year}-{current_time.month:02d}-{current_time.day:02d}-{current_time.hour:02d}"
|
||||
fst_version = "send_" + get_jwt_identity() + "_" + formatted_time
|
||||
|
||||
# 2. 循环处理每条数据
|
||||
for idx, item in enumerate(data_list):
|
||||
try:
|
||||
# print("item",item)
|
||||
# 验证必要字段
|
||||
if "bag_name" not in item or not item["bag_name"]:
|
||||
error_records.append(
|
||||
{
|
||||
"index": idx,
|
||||
"reason": "缺少bag_name或bag_name为空",
|
||||
"data": item,
|
||||
}
|
||||
)
|
||||
continue
|
||||
|
||||
bag_name=item["bag_name"]
|
||||
datetime_full, video_url = extract_datetime_from_filename(bag_name)
|
||||
|
||||
# 创建新的BagFile记录
|
||||
new_bag = BagFile(
|
||||
file_name=bag_name,
|
||||
capture_datetime=datetime_full,
|
||||
level1_tag_id=item.get("level1_tag_id"),
|
||||
level2_tag_id=item.get("level2_tag_id"),
|
||||
level3_tag_id=item.get("level3_tag_id"),
|
||||
level4_tag_id=item.get("level4_tag_id"),
|
||||
video_url=item.get("video_url"),
|
||||
comment1=item.get("comment"),
|
||||
user_id=item.get("user_id") or current_user_id,
|
||||
create_time=(
|
||||
datetime.fromisoformat(item["create_time"])
|
||||
if item.get("create_time")
|
||||
else current_time
|
||||
),
|
||||
update_time=current_time,
|
||||
fst_version=fst_version,
|
||||
bag_status=add_status, # 0是标注,1是质检
|
||||
sync_status="SYNC_NOT_READY",
|
||||
status=BagStatus[label_status],
|
||||
)
|
||||
|
||||
print("new_bag", new_bag)
|
||||
|
||||
# 添加到会话
|
||||
db.session.add(new_bag)
|
||||
inserted_count += 1
|
||||
|
||||
# 3. 更新vlm_filter表
|
||||
# 假设根据bag_name和id进行匹配
|
||||
vlm_filter = VlmFilter.query.filter_by(
|
||||
id=item.get("id"), bag_name=item["bag_name"]
|
||||
).first()
|
||||
|
||||
if vlm_filter:
|
||||
vlm_filter.status = 1 # 1是入库
|
||||
vlm_filter.init_label = add_status # 0是标注,1是质检
|
||||
updated_count += 1
|
||||
else:
|
||||
error_records.append(
|
||||
{
|
||||
"index": idx,
|
||||
"reason": f"未找到对应的vlm_filter记录 (id: {item.get('id')}, bag_name: {item['bag_name']})",
|
||||
"data": item,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
error_records.append(
|
||||
{"index": idx, "reason": f"处理错误: {str(e)}", "data": item}
|
||||
)
|
||||
|
||||
# 4. 提交事务
|
||||
db.session.commit()
|
||||
|
||||
# 5. 返回结果
|
||||
return jsonify(
|
||||
{
|
||||
"code": 200,
|
||||
"message": f"批量处理完成,成功插入 {inserted_count} 条记录,{len(error_records)} 条记录处理失败",
|
||||
"data": {
|
||||
"inserted_count": inserted_count,
|
||||
"updated_count": updated_count,
|
||||
"error_count": len(error_records),
|
||||
"errors": error_records if error_records else None,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"【批量插入接口异常】: {str(e)}")
|
||||
return jsonify({"code": 500, "message": f"处理失败:{str(e)}"}), 500
|
||||
Reference in New Issue
Block a user