修改路径,从src放到根目录

This commit is contained in:
zqc
2026-01-08 10:32:36 +08:00
parent 96589ebdbd
commit f86effd63c
37 changed files with 51 additions and 410 deletions

View 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}"
)