Files
video/service/face_service.py

341 lines
12 KiB
Python
Raw Permalink Normal View History

2025-09-03 21:41:26 +08:00
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form
2025-09-03 20:07:38 +08:00
from mysql.connector import Error as MySQLError
from ds.db import db
from schema.face_schema import FaceCreateRequest, FaceUpdateRequest, FaceResponse
from schema.response_schema import APIResponse
from middle.auth_middleware import get_current_user
from schema.user_schema import UserResponse
2025-09-04 22:59:27 +08:00
from util.face_util import add_binary_data,get_average_feature
#初始化实例
2025-09-03 20:07:38 +08:00
router = APIRouter(
prefix="/faces",
tags=["人脸管理"]
)
2025-09-04 10:46:05 +08:00
2025-09-03 20:07:38 +08:00
# ------------------------------
2025-09-08 17:34:23 +08:00
# 1. 创建人脸记录(核心修正: ID 数据库自增、前端无需传)
2025-09-03 20:07:38 +08:00
# ------------------------------
2025-09-08 17:34:23 +08:00
@router.post("", response_model=APIResponse, summary="创建人脸记录(传名称+文件、ID自增")
2025-09-03 20:07:38 +08:00
async def create_face(
2025-09-08 17:34:23 +08:00
# 前端仅需传: name可选、Form格式、file必传、文件
2025-09-03 21:41:26 +08:00
name: str = Form(None, max_length=255, description="名称(可选)"),
2025-09-08 17:34:23 +08:00
file: UploadFile = File(..., description="人脸文件(必传、暂不处理内容)")
2025-09-03 20:07:38 +08:00
):
"""
2025-09-08 17:34:23 +08:00
创建人脸记录:
2025-09-03 20:07:38 +08:00
- 需登录认证
2025-09-08 17:34:23 +08:00
- 前端传参: multipart/form-data 表单name 可选file 必传
- ID 由数据库自动生成无需前端传入
- 暂不处理文件内容eigenvalue 设为 None
2025-09-03 20:07:38 +08:00
"""
2025-09-04 22:59:27 +08:00
# 调用你的方法
2025-09-03 20:07:38 +08:00
conn = None
cursor = None
try:
2025-09-08 17:34:23 +08:00
# 1. 用模型校验 name仅校验长度、无需ID
2025-09-03 21:41:26 +08:00
face_create = FaceCreateRequest(name=name)
2025-09-03 20:07:38 +08:00
conn = db.get_connection()
cursor = conn.cursor(dictionary=True)
2025-09-03 21:41:26 +08:00
# 把文件转为二进制数组
file_content = await file.read()
2025-09-04 22:59:27 +08:00
# 计算特征值
flag, eigenvalue = add_binary_data(file_content)
if flag == False:
raise HTTPException(
status_code=500,
detail="未检测到人脸"
)
# 打印数组长度
2025-09-08 17:34:23 +08:00
print(f"文件大小: {len(file_content)} 字节")
2025-09-03 21:41:26 +08:00
2025-09-08 17:34:23 +08:00
# 2. 插入数据库: 无需传 ID自增、只传 name 和 eigenvalueNone
2025-09-03 20:07:38 +08:00
insert_query = """
2025-09-03 21:41:26 +08:00
INSERT INTO face (name, eigenvalue)
VALUES (%s, %s)
2025-09-03 20:07:38 +08:00
"""
2025-09-04 22:59:27 +08:00
cursor.execute(insert_query, (face_create.name, str(eigenvalue)))
2025-09-03 20:07:38 +08:00
conn.commit()
2025-09-08 17:34:23 +08:00
# 3. 获取数据库自动生成的 ID关键: 用 LAST_INSERT_ID() 查刚插入的记录)
2025-09-03 21:41:26 +08:00
select_new_query = "SELECT * FROM face WHERE id = LAST_INSERT_ID()"
cursor.execute(select_new_query)
2025-09-03 20:07:38 +08:00
created_face = cursor.fetchone()
2025-09-04 22:59:27 +08:00
if not created_face:
raise HTTPException(
status_code=500,
2025-09-08 17:34:23 +08:00
detail="创建人脸记录成功、但无法获取新创建的记录"
2025-09-04 22:59:27 +08:00
)
2025-09-03 20:07:38 +08:00
return APIResponse(
2025-09-03 21:41:26 +08:00
code=201,
2025-09-08 17:34:23 +08:00
message=f"人脸记录创建成功ID: {created_face['id']}、文件名: {file.filename}",
2025-09-04 22:59:27 +08:00
data=FaceResponse(** created_face)
2025-09-03 20:07:38 +08:00
)
except MySQLError as e:
if conn:
conn.rollback()
2025-09-04 22:59:27 +08:00
# 改为使用HTTPException
raise HTTPException(
status_code=500,
2025-09-08 17:34:23 +08:00
detail=f"创建人脸记录失败: {str(e)}"
2025-09-04 22:59:27 +08:00
) from e
except Exception as e:
# 捕获其他可能的异常
raise HTTPException(
status_code=500,
2025-09-08 17:34:23 +08:00
detail=f"服务器错误: {str(e)}"
2025-09-04 22:59:27 +08:00
) from e
2025-09-03 20:07:38 +08:00
finally:
2025-09-03 21:41:26 +08:00
await file.close() # 关闭文件流
2025-09-03 20:07:38 +08:00
db.close_connection(conn, cursor)
2025-09-04 22:59:27 +08:00
# 调用人脸识别得到特征值(这里可以添加你的人脸识别逻辑)
flag, eigenvalue = add_binary_data(file_content)
if flag == False:
raise HTTPException(
status_code=500,
detail="未检测到人脸"
)
# 将 eigenvalue 转为 str
eigenvalue = str(eigenvalue)
2025-09-03 20:07:38 +08:00
# ------------------------------
2025-09-08 17:34:23 +08:00
# 2. 获取单个人脸记录不变、用自增ID查询
2025-09-03 20:07:38 +08:00
# ------------------------------
@router.get("/{face_id}", response_model=APIResponse, summary="获取单个人脸记录")
async def get_face(
2025-09-08 17:34:23 +08:00
face_id: int, # 这里的 ID 是数据库自增的、前端从创建响应中获取
2025-09-03 21:41:26 +08:00
current_user: UserResponse = Depends(get_current_user)
2025-09-03 20:07:38 +08:00
):
conn = None
cursor = None
try:
conn = db.get_connection()
cursor = conn.cursor(dictionary=True)
query = "SELECT * FROM face WHERE id = %s"
cursor.execute(query, (face_id,))
face = cursor.fetchone()
if not face:
raise HTTPException(
status_code=404,
detail=f"ID为 {face_id} 的人脸记录不存在"
)
return APIResponse(
code=200,
message="人脸记录查询成功",
data=FaceResponse(**face)
)
except MySQLError as e:
2025-09-04 22:59:27 +08:00
# 改为使用HTTPException
raise HTTPException(
status_code=500,
2025-09-08 17:34:23 +08:00
detail=f"查询人脸记录失败: {str(e)}"
2025-09-04 22:59:27 +08:00
) from e
2025-09-03 20:07:38 +08:00
finally:
db.close_connection(conn, cursor)
2025-09-04 22:59:27 +08:00
# 后续 3.获取所有、4.更新、5.删除 接口(修复异常处理)
2025-09-03 20:07:38 +08:00
# ------------------------------
2025-09-03 21:41:26 +08:00
# 3. 获取所有人脸记录(不变)
2025-09-03 20:07:38 +08:00
# ------------------------------
@router.get("", response_model=APIResponse, summary="获取所有人脸记录")
async def get_all_faces(
):
conn = None
cursor = None
try:
conn = db.get_connection()
cursor = conn.cursor(dictionary=True)
2025-09-03 21:41:26 +08:00
query = "SELECT * FROM face ORDER BY id" # 按自增ID排序
2025-09-03 20:07:38 +08:00
cursor.execute(query)
faces = cursor.fetchall()
return APIResponse(
code=200,
message="所有人脸记录查询成功",
2025-09-04 22:59:27 +08:00
data=[FaceResponse(** face) for face in faces]
2025-09-03 20:07:38 +08:00
)
except MySQLError as e:
2025-09-04 22:59:27 +08:00
raise HTTPException(
status_code=500,
2025-09-08 17:34:23 +08:00
detail=f"查询所有人脸记录失败: {str(e)}"
2025-09-04 22:59:27 +08:00
) from e
2025-09-03 20:07:38 +08:00
finally:
db.close_connection(conn, cursor)
# ------------------------------
2025-09-08 17:34:23 +08:00
# 4. 更新人脸记录不变、用自增ID更新
2025-09-03 20:07:38 +08:00
# ------------------------------
@router.put("/{face_id}", response_model=APIResponse, summary="更新人脸记录")
async def update_face(
face_id: int,
face_update: FaceUpdateRequest,
2025-09-03 21:41:26 +08:00
current_user: UserResponse = Depends(get_current_user)
2025-09-03 20:07:38 +08:00
):
conn = None
cursor = None
try:
conn = db.get_connection()
cursor = conn.cursor(dictionary=True)
2025-09-03 21:41:26 +08:00
# 检查记录是否存在
2025-09-03 20:07:38 +08:00
check_query = "SELECT id FROM face WHERE id = %s"
cursor.execute(check_query, (face_id,))
existing_face = cursor.fetchone()
if not existing_face:
raise HTTPException(
status_code=404,
detail=f"ID为 {face_id} 的人脸记录不存在"
)
2025-09-03 21:41:26 +08:00
# 构建更新语句
2025-09-03 20:07:38 +08:00
update_fields = []
params = []
if face_update.name is not None:
update_fields.append("name = %s")
params.append(face_update.name)
if face_update.eigenvalue is not None:
update_fields.append("eigenvalue = %s")
params.append(face_update.eigenvalue)
if not update_fields:
2025-09-03 21:41:26 +08:00
raise HTTPException(status_code=400, detail="至少需提供一个更新字段")
2025-09-03 20:07:38 +08:00
2025-09-03 21:41:26 +08:00
params.append(face_id)
update_query = f"UPDATE face SET {', '.join(update_fields)}, updated_at = CURRENT_TIMESTAMP WHERE id = %s"
2025-09-03 20:07:38 +08:00
cursor.execute(update_query, params)
conn.commit()
2025-09-03 21:41:26 +08:00
# 查询更新后记录
2025-09-03 20:07:38 +08:00
select_query = "SELECT * FROM face WHERE id = %s"
cursor.execute(select_query, (face_id,))
updated_face = cursor.fetchone()
return APIResponse(
code=200,
message="人脸记录更新成功",
data=FaceResponse(**updated_face)
)
except MySQLError as e:
if conn:
conn.rollback()
2025-09-04 22:59:27 +08:00
raise HTTPException(
status_code=500,
2025-09-08 17:34:23 +08:00
detail=f"更新人脸记录失败: {str(e)}"
2025-09-04 22:59:27 +08:00
) from e
2025-09-03 20:07:38 +08:00
finally:
db.close_connection(conn, cursor)
# ------------------------------
2025-09-08 17:34:23 +08:00
# 5. 删除人脸记录不变、用自增ID删除
2025-09-03 20:07:38 +08:00
# ------------------------------
@router.delete("/{face_id}", response_model=APIResponse, summary="删除人脸记录")
async def delete_face(
face_id: int,
2025-09-03 21:41:26 +08:00
current_user: UserResponse = Depends(get_current_user)
2025-09-03 20:07:38 +08:00
):
conn = None
cursor = None
try:
conn = db.get_connection()
cursor = conn.cursor(dictionary=True)
check_query = "SELECT id FROM face WHERE id = %s"
cursor.execute(check_query, (face_id,))
existing_face = cursor.fetchone()
if not existing_face:
raise HTTPException(
status_code=404,
detail=f"ID为 {face_id} 的人脸记录不存在"
)
delete_query = "DELETE FROM face WHERE id = %s"
cursor.execute(delete_query, (face_id,))
conn.commit()
return APIResponse(
code=200,
message=f"ID为 {face_id} 的人脸记录删除成功",
data=None
)
except MySQLError as e:
if conn:
conn.rollback()
2025-09-04 22:59:27 +08:00
raise HTTPException(
status_code=500,
2025-09-08 17:34:23 +08:00
detail=f"删除人脸记录失败: {str(e)}"
2025-09-04 22:59:27 +08:00
) from e
2025-09-03 20:07:38 +08:00
finally:
db.close_connection(conn, cursor)
2025-09-03 22:06:55 +08:00
def get_all_face_name_with_eigenvalue() -> dict:
"""
2025-09-08 17:34:23 +08:00
获取所有人脸的名称及其对应的特征值组成字典返回
2025-09-03 22:06:55 +08:00
key: 人脸名称name
2025-09-08 17:34:23 +08:00
value: 人脸特征值eigenvalue若名称重复则返回平均特征值
: 过滤掉name为None的记录避免字典key为None的情况
2025-09-03 22:06:55 +08:00
"""
conn = None
cursor = None
try:
2025-09-04 22:59:27 +08:00
# 1. 建立数据库连接并获取游标dictionary=True使结果以字典形式返回
2025-09-03 22:06:55 +08:00
conn = db.get_connection()
cursor = conn.cursor(dictionary=True)
2025-09-08 17:34:23 +08:00
# 2. 执行SQL查询: 只获取name非空的记录、减少数据传输
2025-09-03 22:06:55 +08:00
query = "SELECT name, eigenvalue FROM face WHERE name IS NOT NULL"
cursor.execute(query)
2025-09-08 17:34:23 +08:00
faces = cursor.fetchall() # 返回结果: 列表套字典、如 [{"name":"张三","eigenvalue":...}, ...]
2025-09-03 22:06:55 +08:00
2025-09-04 22:59:27 +08:00
# 3. 收集同一名称对应的所有特征值(处理名称重复场景)
2025-09-04 10:46:05 +08:00
name_to_eigenvalues = {}
for face in faces:
name = face["name"]
eigenvalue = face["eigenvalue"]
2025-09-08 17:34:23 +08:00
# 若名称已存在、追加特征值;否则新建列表存储
2025-09-04 10:46:05 +08:00
if name in name_to_eigenvalues:
name_to_eigenvalues[name].append(eigenvalue)
else:
name_to_eigenvalues[name] = [eigenvalue]
2025-09-08 17:34:23 +08:00
# 4. 构建最终字典: 重复名称取平均、唯一名称直接取特征值
2025-09-04 10:46:05 +08:00
face_dict = {}
for name, eigenvalues in name_to_eigenvalues.items():
2025-09-04 22:59:27 +08:00
2025-09-08 17:34:23 +08:00
# 处理特征值: 多个则求平均、单个则直接使用
2025-09-04 10:46:05 +08:00
if len(eigenvalues) > 1:
2025-09-04 22:59:27 +08:00
# 调用外部方法计算平均特征值需确保binary_face_feature_handler已正确导入
face_dict[name] = get_average_feature(eigenvalues)
2025-09-04 10:46:05 +08:00
else:
2025-09-04 22:59:27 +08:00
# 取列表中唯一的特征值避免value为列表类型
2025-09-04 10:46:05 +08:00
face_dict[name] = eigenvalues[0]
2025-09-03 22:06:55 +08:00
return face_dict
except MySQLError as e:
2025-09-08 17:34:23 +08:00
# 捕获数据库异常、添加上下文信息后重新抛出(便于定位问题)
raise Exception(f"获取人脸名称与特征值失败: {str(e)}") from e
2025-09-03 22:06:55 +08:00
finally:
2025-09-08 17:34:23 +08:00
# 5. 无论是否异常、均释放数据库连接和游标(避免资源泄漏)
2025-09-04 22:59:27 +08:00
db.close_connection(conn, cursor)