导出优化,上传端代码
This commit is contained in:
@@ -4,6 +4,7 @@ from sqlalchemy.orm import Session
|
||||
from typing import Optional, Dict, Any
|
||||
from ..core.database import get_db
|
||||
from ..core.response_code import ResponseCode, ResponseMessage
|
||||
from ..core.exceptions import BusinessException, DataNotFoundException, AccountNotFoundException
|
||||
from ..schemas.export_excel import ExportExcelRequest, ExportSettlementRequest
|
||||
from ..services.section_data import SectionDataService
|
||||
from ..services.export_excel import ExportExcelService
|
||||
@@ -133,18 +134,44 @@ def export_settlement_data(
|
||||
media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
)
|
||||
|
||||
except ValueError as ve:
|
||||
logger.warning(f"导出沉降数据失败: {str(ve)}")
|
||||
return {
|
||||
"code": ResponseCode.PARAM_ERROR,
|
||||
"message": str(ve),
|
||||
"data": None
|
||||
}
|
||||
except AccountNotFoundException as e:
|
||||
logger.warning(f"账号不存在: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail={
|
||||
"code": e.code,
|
||||
"message": e.message,
|
||||
"data": None
|
||||
}
|
||||
)
|
||||
except DataNotFoundException as e:
|
||||
logger.warning(f"数据不存在: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail={
|
||||
"code": e.code,
|
||||
"message": e.message,
|
||||
"data": None
|
||||
}
|
||||
)
|
||||
except BusinessException as e:
|
||||
logger.warning(f"业务异常: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={
|
||||
"code": e.code,
|
||||
"message": e.message,
|
||||
"data": None
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"导出沉降数据失败: {str(e)}", exc_info=True)
|
||||
return {
|
||||
"code": ResponseCode.EXPORT_FAILED,
|
||||
"message": f"{ResponseMessage.EXPORT_FAILED}: {str(e)}",
|
||||
"data": None
|
||||
}
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={
|
||||
"code": ResponseCode.EXPORT_FAILED,
|
||||
"message": f"{ResponseMessage.EXPORT_FAILED}: {str(e)}",
|
||||
"data": None
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
59
app/core/exceptions.py
Normal file
59
app/core/exceptions.py
Normal file
@@ -0,0 +1,59 @@
|
||||
"""
|
||||
自定义业务异常类
|
||||
用于区分业务逻辑错误和系统错误
|
||||
"""
|
||||
|
||||
from .response_code import ResponseCode, ResponseMessage
|
||||
|
||||
|
||||
class BusinessException(Exception):
|
||||
"""业务异常基类"""
|
||||
def __init__(self, message: str, code: int = None):
|
||||
self.message = message
|
||||
self.code = code or ResponseCode.INTERNAL_ERROR
|
||||
super().__init__(self.message)
|
||||
|
||||
|
||||
class DataNotFoundException(BusinessException):
|
||||
"""数据不存在异常"""
|
||||
def __init__(self, message: str = None):
|
||||
super().__init__(
|
||||
message or ResponseMessage.DATA_NOT_FOUND,
|
||||
ResponseCode.DATA_NOT_FOUND
|
||||
)
|
||||
|
||||
|
||||
class AccountNotFoundException(BusinessException):
|
||||
"""账号不存在异常"""
|
||||
def __init__(self, message: str = None):
|
||||
super().__init__(
|
||||
message or ResponseMessage.ACCOUNT_NOT_FOUND,
|
||||
ResponseCode.ACCOUNT_NOT_FOUND
|
||||
)
|
||||
|
||||
|
||||
class DataExistsException(BusinessException):
|
||||
"""数据已存在异常"""
|
||||
def __init__(self, message: str = None):
|
||||
super().__init__(
|
||||
message or ResponseMessage.DATA_EXISTS,
|
||||
ResponseCode.DATA_EXISTS
|
||||
)
|
||||
|
||||
|
||||
class ValidationException(BusinessException):
|
||||
"""数据验证异常"""
|
||||
def __init__(self, message: str = None):
|
||||
super().__init__(
|
||||
message or ResponseMessage.BAD_REQUEST,
|
||||
ResponseCode.VALIDATION_ERROR
|
||||
)
|
||||
|
||||
|
||||
class ExportException(BusinessException):
|
||||
"""导出异常"""
|
||||
def __init__(self, message: str = None):
|
||||
super().__init__(
|
||||
message or ResponseMessage.EXPORT_FAILED,
|
||||
ResponseCode.EXPORT_FAILED
|
||||
)
|
||||
@@ -10,6 +10,7 @@ from ..services.checkpoint import CheckpointService
|
||||
from ..services.settlement_data import SettlementDataService
|
||||
from ..services.level_data import LevelDataService
|
||||
from ..services.account import AccountService
|
||||
from ..core.exceptions import DataNotFoundException, AccountNotFoundException
|
||||
import pandas as pd
|
||||
import logging
|
||||
from datetime import datetime
|
||||
@@ -37,7 +38,7 @@ class ExportExcelService:
|
||||
settlement_data: SettlementData,
|
||||
section_data: SectionData,
|
||||
checkpoint_data: Checkpoint,
|
||||
level_data: LevelData) -> Dict[str, Any]:
|
||||
level_data: Optional[LevelData]) -> Dict[str, Any]:
|
||||
"""
|
||||
合并沉降数据与关联数据,去除重复和id字段
|
||||
"""
|
||||
@@ -75,20 +76,21 @@ class ExportExcelService:
|
||||
result[f"观测点_{key}"] = value
|
||||
|
||||
# 水准数据字段映射(添加前缀)
|
||||
level_comments = self.get_field_comments(LevelData)
|
||||
level_dict = level_data.to_dict()
|
||||
for field_name, value in level_dict.items():
|
||||
# 跳过id和NYID字段(NYID可能重复)
|
||||
if field_name in ['id', 'NYID']:
|
||||
continue
|
||||
key = level_comments.get(field_name, field_name)
|
||||
result[f"水准_{key}"] = value
|
||||
if level_data is not None:
|
||||
level_comments = self.get_field_comments(LevelData)
|
||||
level_dict = level_data.to_dict()
|
||||
for field_name, value in level_dict.items():
|
||||
# 跳过id和NYID字段(NYID可能重复)
|
||||
if field_name in ['id', 'NYID']:
|
||||
continue
|
||||
key = level_comments.get(field_name, field_name)
|
||||
result[f"水准_{key}"] = value
|
||||
|
||||
return result
|
||||
|
||||
def export_settlement_data_to_file(self, db: Session, project_name: str, file_path: str):
|
||||
"""
|
||||
根据项目名称导出沉降数据Excel文件到指定路径
|
||||
根据项目名称导出沉降数据Excel文件到指定路径(批量查询优化版本)
|
||||
"""
|
||||
logger.info(f"开始导出项目 '{project_name}' 的沉降数据到文件: {file_path}")
|
||||
|
||||
@@ -96,63 +98,92 @@ class ExportExcelService:
|
||||
account_responses = self.account_service.search_accounts(db, project_name=project_name)
|
||||
if not account_responses:
|
||||
logger.warning(f"未找到项目名称为 '{project_name}' 的账号")
|
||||
raise ValueError(f"未找到项目名称为 '{project_name}' 的账号")
|
||||
raise AccountNotFoundException(f"未找到项目名称为 '{project_name}' 的账号")
|
||||
|
||||
account_response = account_responses[0]
|
||||
account_id = str(account_response.id)
|
||||
account_id = str(account_response.account_id)
|
||||
logger.info(f"找到账号 ID: {account_id}")
|
||||
|
||||
# 2. 通过 account_id 查询断面数据
|
||||
sections = self.section_service.search_section_data(db, account_id=account_id, limit=10000)
|
||||
if not sections:
|
||||
logger.warning(f"账号 {account_id} 下未找到断面数据")
|
||||
raise ValueError(f"账号 {account_id} 下未找到断面数据")
|
||||
raise DataNotFoundException(f"账号 {account_id} 下未找到断面数据")
|
||||
|
||||
logger.info(f"找到 {len(sections)} 个断面")
|
||||
|
||||
all_settlement_records = []
|
||||
# 3. 收集所有观测点数据,建立断面->观测点映射
|
||||
section_dict = {section.section_id: section for section in sections}
|
||||
section_checkpoint_map = {} # section_id -> [checkpoints]
|
||||
all_checkpoints = []
|
||||
|
||||
# 3-6. 遍历断面数据,查询关联数据
|
||||
for section in sections:
|
||||
section_id = section.section_id
|
||||
logger.debug(f"处理断面: {section_id}")
|
||||
checkpoints = self.checkpoint_service.get_by_section_id(db, section.section_id)
|
||||
if checkpoints:
|
||||
section_checkpoint_map[section.section_id] = checkpoints
|
||||
all_checkpoints.extend(checkpoints)
|
||||
|
||||
# 3. 通过断面数据的section_id查询观测点数据
|
||||
checkpoints = self.checkpoint_service.get_by_section_id(db, section_id)
|
||||
if not checkpoints:
|
||||
logger.debug(f"断面 {section_id} 下未找到观测点数据")
|
||||
continue
|
||||
if not all_checkpoints:
|
||||
logger.warning("未找到任何观测点数据")
|
||||
raise DataNotFoundException("未找到任何观测点数据")
|
||||
|
||||
logger.info(f"找到 {len(all_checkpoints)} 个观测点")
|
||||
|
||||
# 4. 批量查询沉降数据(关键优化点)
|
||||
point_ids = [cp.point_id for cp in all_checkpoints]
|
||||
logger.info(f"开始批量查询 {len(point_ids)} 个观测点的沉降数据")
|
||||
all_settlements = self.settlement_service.get_by_point_ids(db, point_ids)
|
||||
|
||||
if not all_settlements:
|
||||
logger.warning("未找到任何沉降数据")
|
||||
logger.info(f"观测点id集合{point_ids}")
|
||||
raise DataNotFoundException("未找到任何沉降数据")
|
||||
|
||||
logger.info(f"批量查询到 {len(all_settlements)} 条沉降数据")
|
||||
|
||||
# 5. 建立观测点->沉降数据映射
|
||||
checkpoint_dict = {cp.point_id: cp for cp in all_checkpoints}
|
||||
point_settlement_map = {} # point_id -> [settlements]
|
||||
nyid_set = set()
|
||||
|
||||
for settlement in all_settlements:
|
||||
if settlement.point_id not in point_settlement_map:
|
||||
point_settlement_map[settlement.point_id] = []
|
||||
point_settlement_map[settlement.point_id].append(settlement)
|
||||
if settlement.NYID:
|
||||
nyid_set.add(settlement.NYID)
|
||||
|
||||
# 6. 批量查询水准数据(关键优化点)
|
||||
nyid_list = list(nyid_set)
|
||||
logger.info(f"开始批量查询 {len(nyid_list)} 个期数的水准数据")
|
||||
all_level_data = self.level_service.get_by_nyids(db, nyid_list)
|
||||
logger.info(f"批量查询到 {len(all_level_data)} 条水准数据")
|
||||
|
||||
# 建立NYID->水准数据映射
|
||||
nyid_level_map = {}
|
||||
for level_data in all_level_data:
|
||||
if level_data.NYID not in nyid_level_map:
|
||||
nyid_level_map[level_data.NYID] = level_data
|
||||
|
||||
# 7. 合并数据
|
||||
all_settlement_records = []
|
||||
for section in sections:
|
||||
checkpoints = section_checkpoint_map.get(section.section_id, [])
|
||||
for checkpoint in checkpoints:
|
||||
point_id = checkpoint.point_id
|
||||
|
||||
# 4. 通过观测点数据的point_id查询沉降数据集
|
||||
settlements = self.settlement_service.get_by_point_id(db, point_id)
|
||||
if not settlements:
|
||||
logger.debug(f"观测点 {point_id} 下未找到沉降数据")
|
||||
continue
|
||||
|
||||
settlements = point_settlement_map.get(checkpoint.point_id, [])
|
||||
for settlement in settlements:
|
||||
nyid = settlement.NYID
|
||||
# 从映射中获取水准数据
|
||||
level_data = nyid_level_map.get(settlement.NYID)
|
||||
|
||||
# 5. 通过沉降数据的NYID查询水准数据
|
||||
level_data_list = self.level_service.get_by_nyid(db, nyid)
|
||||
if not level_data_list:
|
||||
logger.warning(f"期数 {nyid} 下未找到水准数据")
|
||||
# 即使没有水准数据,也继续处理
|
||||
level_data = None
|
||||
else:
|
||||
level_data = level_data_list[0]
|
||||
|
||||
# 6. 合并数据
|
||||
# 合并数据
|
||||
merged_record = self.merge_settlement_with_related_data(
|
||||
db, settlement, section, checkpoint, level_data
|
||||
)
|
||||
all_settlement_records.append(merged_record)
|
||||
|
||||
if not all_settlement_records:
|
||||
logger.warning("未找到任何沉降数据")
|
||||
raise ValueError("未找到任何沉降数据")
|
||||
logger.warning("未能合并任何数据记录")
|
||||
raise DataNotFoundException("未能合并任何数据记录")
|
||||
|
||||
logger.info(f"共找到 {len(all_settlement_records)} 条沉降数据记录")
|
||||
|
||||
|
||||
@@ -20,6 +20,12 @@ class SettlementDataService(BaseService[SettlementData]):
|
||||
"""根据观测点ID获取沉降数据"""
|
||||
return self.get_by_field(db, "point_id", point_id)
|
||||
|
||||
def get_by_point_ids(self, db: Session, point_ids: List[str]) -> List[SettlementData]:
|
||||
"""根据观测点ID列表批量获取沉降数据"""
|
||||
if not point_ids:
|
||||
return []
|
||||
return db.query(SettlementData).filter(SettlementData.point_id.in_(point_ids)).all()
|
||||
|
||||
def get_by_nyid(self, db: Session, nyid: str) -> List[SettlementData]:
|
||||
"""根据期数ID获取沉降数据"""
|
||||
return self.get_by_field(db, "NYID", nyid)
|
||||
|
||||
Reference in New Issue
Block a user