""" 人脸特征业务逻辑服务层 """ import logging from datetime import datetime from typing import Optional, List, Dict, Any from contextlib import contextmanager from src.repositories.face_feature_repository import FaceFeatureRepository from src.schemas.face_feature import ( FaceFeatureCreate, FaceFeatureUpdate, FaceFeatureQuery, FaceFeatureResponse, FaceFeatureListResponse, FaceFeatureStatsResponse, BatchFaceFeatureCreate, FeatureStatus ) from src.models.face_feature import FeatureStatusEnum from src.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}" )