修改路径,从src放到根目录
This commit is contained in:
562
services/face_feature_service.py
Normal file
562
services/face_feature_service.py
Normal file
@@ -0,0 +1,562 @@
|
||||
"""
|
||||
人脸特征业务逻辑服务层
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Dict, Any
|
||||
from contextlib import contextmanager
|
||||
|
||||
from repositories.face_feature_repository import FaceFeatureRepository
|
||||
from schemas.face_feature import (
|
||||
FaceFeatureCreate,
|
||||
FaceFeatureUpdate,
|
||||
FaceFeatureQuery,
|
||||
FaceFeatureResponse,
|
||||
FaceFeatureListResponse,
|
||||
FaceFeatureStatsResponse,
|
||||
BatchFaceFeatureCreate,
|
||||
FeatureStatus
|
||||
)
|
||||
from models.face_feature import FeatureStatusEnum
|
||||
from utils.logger import setup_logger
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
|
||||
class FaceFeatureService:
|
||||
"""人脸特征业务服务"""
|
||||
|
||||
def __init__(self, repository: FaceFeatureRepository):
|
||||
"""
|
||||
初始化服务
|
||||
|
||||
Args:
|
||||
repository: 特征仓库实例
|
||||
"""
|
||||
self.repository = repository
|
||||
|
||||
# ===== CRUD操作 =====
|
||||
|
||||
def create_feature(self, feature_data: FaceFeatureCreate) -> FaceFeatureResponse:
|
||||
"""
|
||||
创建特征记录
|
||||
|
||||
Args:
|
||||
feature_data: 特征数据
|
||||
|
||||
Returns:
|
||||
创建的特征记录响应
|
||||
"""
|
||||
logger.info(f"Creating face feature for person_id={feature_data.person_id}")
|
||||
|
||||
# 业务逻辑验证
|
||||
self._validate_feature_data(feature_data)
|
||||
|
||||
# 检查是否已存在相同记录
|
||||
if feature_data.feature_type is not None:
|
||||
existing = self.repository.get_by_person_and_type(
|
||||
feature_data.person_id,
|
||||
feature_data.feature_type
|
||||
)
|
||||
if existing:
|
||||
raise ValueError(
|
||||
f"Feature record already exists for person_id={feature_data.person_id}, "
|
||||
f"feature_type={feature_data.feature_type}"
|
||||
)
|
||||
|
||||
# 创建记录
|
||||
feature = self.repository.create(feature_data)
|
||||
|
||||
# 转换为响应模型
|
||||
return FaceFeatureResponse.model_validate(feature)
|
||||
|
||||
def create_features_batch(self, batch_data: BatchFaceFeatureCreate) -> List[FaceFeatureResponse]:
|
||||
"""
|
||||
批量创建特征记录
|
||||
|
||||
Args:
|
||||
batch_data: 批量特征数据
|
||||
|
||||
Returns:
|
||||
创建的特征记录响应列表
|
||||
"""
|
||||
logger.info(f"Creating {len(batch_data.items)} face features in batch")
|
||||
|
||||
# 验证所有数据
|
||||
for feature_data in batch_data.items:
|
||||
self._validate_feature_data(feature_data)
|
||||
|
||||
# 批量创建
|
||||
features = self.repository.create_batch(batch_data.items)
|
||||
|
||||
# 转换为响应模型
|
||||
return [FaceFeatureResponse.model_validate(f) for f in features]
|
||||
|
||||
def get_feature(self, feature_id: int) -> Optional[FaceFeatureResponse]:
|
||||
"""
|
||||
获取特征记录
|
||||
|
||||
Args:
|
||||
feature_id: 特征记录ID
|
||||
|
||||
Returns:
|
||||
特征记录响应或None
|
||||
"""
|
||||
logger.debug(f"Getting face feature: id={feature_id}")
|
||||
|
||||
feature = self.repository.get_by_id(feature_id)
|
||||
if not feature:
|
||||
return None
|
||||
|
||||
return FaceFeatureResponse.model_validate(feature)
|
||||
|
||||
def get_feature_by_person_and_type(
|
||||
self,
|
||||
person_id: int,
|
||||
feature_type: int
|
||||
) -> Optional[FaceFeatureResponse]:
|
||||
"""
|
||||
根据人员ID和特征类型获取特征记录
|
||||
|
||||
Args:
|
||||
person_id: 人员ID
|
||||
feature_type: 特征类型
|
||||
|
||||
Returns:
|
||||
特征记录响应或None
|
||||
"""
|
||||
logger.debug(f"Getting face feature: person_id={person_id}, feature_type={feature_type}")
|
||||
|
||||
feature = self.repository.get_by_person_and_type(person_id, feature_type)
|
||||
if not feature:
|
||||
return None
|
||||
|
||||
return FaceFeatureResponse.model_validate(feature)
|
||||
|
||||
def list_features_by_person(
|
||||
self,
|
||||
person_id: int,
|
||||
limit: int = 100
|
||||
) -> List[FaceFeatureResponse]:
|
||||
"""
|
||||
获取人员的特征记录列表
|
||||
|
||||
Args:
|
||||
person_id: 人员ID
|
||||
limit: 返回数量限制
|
||||
|
||||
Returns:
|
||||
特征记录响应列表
|
||||
"""
|
||||
logger.debug(f"Listing face features for person_id={person_id}")
|
||||
|
||||
features = self.repository.get_by_person(person_id, limit)
|
||||
return [FaceFeatureResponse.model_validate(f) for f in features]
|
||||
|
||||
def query_features(
|
||||
self,
|
||||
query: FaceFeatureQuery,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
order_by: str = "created_time",
|
||||
desc_order: bool = True
|
||||
) -> FaceFeatureListResponse:
|
||||
"""
|
||||
查询特征记录
|
||||
|
||||
Args:
|
||||
query: 查询条件
|
||||
page: 页码
|
||||
page_size: 每页数量
|
||||
order_by: 排序字段
|
||||
desc_order: 是否降序
|
||||
|
||||
Returns:
|
||||
特征记录列表响应
|
||||
"""
|
||||
logger.debug(f"Querying face features with filters: {query.model_dump(exclude_unset=True)}")
|
||||
|
||||
features, total = self.repository.query_features(
|
||||
query, page, page_size, order_by, desc_order
|
||||
)
|
||||
|
||||
items = [FaceFeatureResponse.model_validate(f) for f in features]
|
||||
|
||||
return FaceFeatureListResponse(
|
||||
total=total,
|
||||
items=items,
|
||||
page=page,
|
||||
page_size=page_size
|
||||
)
|
||||
|
||||
def update_feature(
|
||||
self,
|
||||
feature_id: int,
|
||||
update_data: FaceFeatureUpdate
|
||||
) -> Optional[FaceFeatureResponse]:
|
||||
"""
|
||||
更新特征记录
|
||||
|
||||
Args:
|
||||
feature_id: 特征记录ID
|
||||
update_data: 更新数据
|
||||
|
||||
Returns:
|
||||
更新后的特征记录响应或None
|
||||
"""
|
||||
logger.info(f"Updating face feature: id={feature_id}")
|
||||
|
||||
# 业务逻辑验证
|
||||
if update_data.status is not None:
|
||||
self._validate_status_transition(feature_id, update_data.status)
|
||||
|
||||
# 更新记录
|
||||
feature = self.repository.update(feature_id, update_data)
|
||||
if not feature:
|
||||
return None
|
||||
|
||||
return FaceFeatureResponse.model_validate(feature)
|
||||
|
||||
def update_feature_data(self, feature_id: int, feature_data: bytes) -> bool:
|
||||
"""
|
||||
更新特征数据
|
||||
|
||||
Args:
|
||||
feature_id: 特征记录ID
|
||||
feature_data: 特征数据
|
||||
|
||||
Returns:
|
||||
是否成功更新
|
||||
"""
|
||||
logger.info(f"Updating feature data for face feature: id={feature_id}")
|
||||
|
||||
# 验证特征数据
|
||||
if not feature_data or len(feature_data) == 0:
|
||||
raise ValueError("Feature data cannot be empty")
|
||||
|
||||
if len(feature_data) > 1024 * 1024: # 1MB限制
|
||||
raise ValueError("Feature data is too large (max 1MB)")
|
||||
|
||||
return self.repository.update_feature_data(feature_id, feature_data)
|
||||
|
||||
def update_status(
|
||||
self,
|
||||
feature_id: int,
|
||||
status: FeatureStatus,
|
||||
start_time: Optional[datetime] = None,
|
||||
finish_time: Optional[datetime] = None
|
||||
) -> bool:
|
||||
"""
|
||||
更新计算状态
|
||||
|
||||
Args:
|
||||
feature_id: 特征记录ID
|
||||
status: 新状态
|
||||
start_time: 开始时间
|
||||
finish_time: 结束时间
|
||||
|
||||
Returns:
|
||||
是否成功更新
|
||||
"""
|
||||
logger.info(f"Updating status to {status} for face feature: id={feature_id}")
|
||||
|
||||
# 验证状态转换
|
||||
self._validate_status_transition(feature_id, status)
|
||||
|
||||
return self.repository.update_status(feature_id, status, start_time, finish_time)
|
||||
|
||||
def delete_feature(self, feature_id: int) -> bool:
|
||||
"""
|
||||
删除特征记录
|
||||
|
||||
Args:
|
||||
feature_id: 特征记录ID
|
||||
|
||||
Returns:
|
||||
是否成功删除
|
||||
"""
|
||||
logger.info(f"Deleting face feature: id={feature_id}")
|
||||
|
||||
return self.repository.delete(feature_id)
|
||||
|
||||
def delete_features_by_person(self, person_id: int) -> int:
|
||||
"""
|
||||
删除指定人员的所有特征记录
|
||||
|
||||
Args:
|
||||
person_id: 人员ID
|
||||
|
||||
Returns:
|
||||
删除的记录数
|
||||
"""
|
||||
logger.info(f"Deleting all face features for person_id={person_id}")
|
||||
|
||||
return self.repository.delete_by_person(person_id)
|
||||
|
||||
# ===== 业务操作 =====
|
||||
|
||||
def start_processing(self, feature_id: int) -> bool:
|
||||
"""
|
||||
开始处理特征计算
|
||||
|
||||
Args:
|
||||
feature_id: 特征记录ID
|
||||
|
||||
Returns:
|
||||
是否成功开始
|
||||
"""
|
||||
logger.info(f"Starting processing for face feature: id={feature_id}")
|
||||
|
||||
# 获取当前特征
|
||||
feature = self.repository.get_by_id(feature_id)
|
||||
if not feature:
|
||||
return False
|
||||
|
||||
# 验证状态
|
||||
if feature.status != FeatureStatusEnum.NOT_STARTED:
|
||||
raise ValueError(
|
||||
f"Cannot start processing for feature with status {feature.status_name}"
|
||||
)
|
||||
|
||||
# 更新状态
|
||||
return self.repository.update_status(
|
||||
feature_id,
|
||||
FeatureStatus.PROCESSING,
|
||||
start_time=datetime.now()
|
||||
)
|
||||
|
||||
def finish_processing(self, feature_id: int, success: bool = True) -> bool:
|
||||
"""
|
||||
完成特征计算
|
||||
|
||||
Args:
|
||||
feature_id: 特征记录ID
|
||||
success: 是否成功
|
||||
|
||||
Returns:
|
||||
是否成功完成
|
||||
"""
|
||||
logger.info(f"Finishing processing for face feature: id={feature_id}, success={success}")
|
||||
|
||||
# 获取当前特征
|
||||
feature = self.repository.get_by_id(feature_id)
|
||||
if not feature:
|
||||
return False
|
||||
|
||||
# 验证状态
|
||||
if feature.status != FeatureStatusEnum.PROCESSING:
|
||||
raise ValueError(
|
||||
f"Cannot finish processing for feature with status {feature.status_name}"
|
||||
)
|
||||
|
||||
# 更新状态
|
||||
status = FeatureStatus.SUCCESS if success else FeatureStatus.FAILED
|
||||
return self.repository.update_status(
|
||||
feature_id,
|
||||
status,
|
||||
finish_time=datetime.now()
|
||||
)
|
||||
|
||||
def process_pending_features(self, limit: int = 100) -> List[FaceFeatureResponse]:
|
||||
"""
|
||||
处理待计算的特征记录
|
||||
|
||||
Args:
|
||||
limit: 最大处理数量
|
||||
|
||||
Returns:
|
||||
处理中的特征记录列表
|
||||
"""
|
||||
logger.info(f"Processing up to {limit} pending face features")
|
||||
|
||||
features = self.repository.mark_for_processing(limit)
|
||||
return [FaceFeatureResponse.model_validate(f) for f in features]
|
||||
|
||||
# ===== 统计和分析 =====
|
||||
|
||||
def get_statistics(self) -> FaceFeatureStatsResponse:
|
||||
"""
|
||||
获取特征记录统计信息
|
||||
|
||||
Returns:
|
||||
统计信息响应
|
||||
"""
|
||||
logger.debug("Getting face feature statistics")
|
||||
|
||||
stats = self.repository.get_stats()
|
||||
|
||||
# 转换状态枚举名称
|
||||
by_status = {}
|
||||
for status_value, count in stats["by_status"].items():
|
||||
try:
|
||||
status_name = FeatureStatusEnum(int(status_value)).name
|
||||
by_status[status_name] = count
|
||||
except ValueError:
|
||||
by_status[f"未知({status_value})"] = count
|
||||
|
||||
return FaceFeatureStatsResponse(
|
||||
total_count=stats["total_count"],
|
||||
by_status=by_status,
|
||||
by_feature_type=stats["by_feature_type"],
|
||||
avg_processing_time=stats["avg_processing_time"]
|
||||
)
|
||||
|
||||
def get_person_statistics(self, person_id: int) -> Dict[str, Any]:
|
||||
"""
|
||||
获取人员的特征统计信息
|
||||
|
||||
Args:
|
||||
person_id: 人员ID
|
||||
|
||||
Returns:
|
||||
人员统计信息
|
||||
"""
|
||||
logger.debug(f"Getting statistics for person_id={person_id}")
|
||||
|
||||
# 获取该人员的所有特征
|
||||
features = self.repository.get_by_person(person_id, limit=1000)
|
||||
|
||||
if not features:
|
||||
return {
|
||||
"person_id": person_id,
|
||||
"total_features": 0,
|
||||
"status_summary": {},
|
||||
"feature_types": []
|
||||
}
|
||||
|
||||
# 统计信息
|
||||
status_summary = {}
|
||||
feature_types = set()
|
||||
successful_features = []
|
||||
|
||||
for feature in features:
|
||||
# 状态统计
|
||||
status_name = feature.status_name
|
||||
status_summary[status_name] = status_summary.get(status_name, 0) + 1
|
||||
|
||||
# 特征类型
|
||||
if feature.feature_type is not None:
|
||||
feature_types.add(feature.feature_type)
|
||||
|
||||
# 成功完成的特征
|
||||
if feature.status == FeatureStatusEnum.SUCCESS:
|
||||
successful_features.append(feature)
|
||||
|
||||
# 计算平均处理时间(仅成功记录)
|
||||
total_time = 0
|
||||
valid_count = 0
|
||||
|
||||
for feature in successful_features:
|
||||
if feature.processing_time:
|
||||
total_time += feature.processing_time
|
||||
valid_count += 1
|
||||
|
||||
avg_time = total_time / valid_count if valid_count > 0 else None
|
||||
|
||||
return {
|
||||
"person_id": person_id,
|
||||
"total_features": len(features),
|
||||
"status_summary": status_summary,
|
||||
"feature_types": sorted(list(feature_types)),
|
||||
"avg_processing_time": avg_time,
|
||||
"successful_count": len(successful_features)
|
||||
}
|
||||
|
||||
def cleanup_old_records(self, days: int = 30) -> Dict[str, Any]:
|
||||
"""
|
||||
清理旧的特征记录
|
||||
|
||||
Args:
|
||||
days: 保留天数
|
||||
|
||||
Returns:
|
||||
清理结果
|
||||
"""
|
||||
logger.info(f"Cleaning up face features older than {days} days")
|
||||
|
||||
# 先获取清理前的统计
|
||||
before_stats = self.repository.get_stats()
|
||||
|
||||
# 执行清理
|
||||
deleted_count = self.repository.cleanup_old_features(days)
|
||||
|
||||
# 获取清理后的统计
|
||||
after_stats = self.repository.get_stats()
|
||||
|
||||
return {
|
||||
"days_retained": days,
|
||||
"deleted_count": deleted_count,
|
||||
"before_total": before_stats["total_count"],
|
||||
"after_total": after_stats["total_count"],
|
||||
"reduction_percentage": (
|
||||
(before_stats["total_count"] - after_stats["total_count"]) /
|
||||
before_stats["total_count"] * 100
|
||||
if before_stats["total_count"] > 0 else 0
|
||||
)
|
||||
}
|
||||
|
||||
# ===== 私有方法 =====
|
||||
|
||||
def _validate_feature_data(self, feature_data: FaceFeatureCreate) -> None:
|
||||
"""
|
||||
验证特征数据
|
||||
|
||||
Args:
|
||||
feature_data: 特征数据
|
||||
|
||||
Raises:
|
||||
ValueError: 如果数据无效
|
||||
"""
|
||||
# 验证人员ID
|
||||
if feature_data.person_id <= 0:
|
||||
raise ValueError("person_id must be greater than 0")
|
||||
|
||||
# 验证特征类型
|
||||
if feature_data.feature_type is not None and feature_data.feature_type < 0:
|
||||
raise ValueError("feature_type must be non-negative")
|
||||
|
||||
# 验证特征数据大小
|
||||
if feature_data.feature_data and len(feature_data.feature_data) > 1024 * 1024:
|
||||
raise ValueError("feature_data is too large (max 1MB)")
|
||||
|
||||
# 验证状态
|
||||
if feature_data.status:
|
||||
try:
|
||||
FeatureStatus(feature_data.status)
|
||||
except ValueError:
|
||||
raise ValueError(f"Invalid status value: {feature_data.status}")
|
||||
|
||||
def _validate_status_transition(self, feature_id: int, new_status: FeatureStatus) -> None:
|
||||
"""
|
||||
验证状态转换是否有效
|
||||
|
||||
Args:
|
||||
feature_id: 特征记录ID
|
||||
new_status: 新状态
|
||||
|
||||
Raises:
|
||||
ValueError: 如果状态转换无效
|
||||
"""
|
||||
# 获取当前特征
|
||||
feature = self.repository.get_by_id(feature_id)
|
||||
if not feature:
|
||||
return
|
||||
|
||||
current_status = FeatureStatusEnum(feature.status)
|
||||
|
||||
# 定义允许的状态转换
|
||||
allowed_transitions = {
|
||||
FeatureStatusEnum.NOT_STARTED: [FeatureStatusEnum.PROCESSING],
|
||||
FeatureStatusEnum.PROCESSING: [FeatureStatusEnum.SUCCESS, FeatureStatusEnum.FAILED],
|
||||
FeatureStatusEnum.SUCCESS: [],
|
||||
FeatureStatusEnum.FAILED: [FeatureStatusEnum.PROCESSING] # 允许重试
|
||||
}
|
||||
|
||||
new_status_enum = FeatureStatusEnum(new_status.value if isinstance(new_status, FeatureStatus) else new_status)
|
||||
|
||||
# 检查转换是否允许
|
||||
if new_status_enum not in allowed_transitions.get(current_status, []):
|
||||
raise ValueError(
|
||||
f"Cannot transition from {current_status.name} to {new_status_enum.name}"
|
||||
)
|
||||
Reference in New Issue
Block a user