from collections.abc import Mapping from dataclasses import dataclass from datetime import datetime from typing import Any from services.database_service_base import DatabaseServiceBase from database.models import ChildLocationCurrent try: from banban.dao.location import LocationDAO, ParentDeviceAccess from banban.schemas.location import DeviceLocationReportRequest from banban.service.im import im_service except ModuleNotFoundError: from banban.dao.location import LocationDAO, ParentDeviceAccess from banban.schemas.location import DeviceLocationReportRequest from banban.service.im import im_service class LocationService(DatabaseServiceBase): def __init__(self): super().__init__(service_name="location_service") async def assert_parent_device_access(self, *, device_id: str, user_id: int) -> ParentDeviceAccess: db_session = await self.get_session() try: dao = LocationDAO(db_session) return await dao.assert_parent_device_access(device_id=device_id, user_id=user_id) finally: await db_session.close() async def report_device_location( self, *, device_id: str, serial_number: str, payload: DeviceLocationReportRequest, ) -> tuple[Any, Mapping[str, Any]]: device_identity = await im_service.authenticate_device_identity( device_id=device_id, serial_number=serial_number, ) db_session = await self.get_session() try: dao = LocationDAO(db_session) current_row = await dao.report_device_location( device_id=device_id, child_id=device_identity.child_id, payload=payload, ) return device_identity, current_row finally: await db_session.close() async def get_device_current_location( self, *, device_id: str, user_id: int, ) -> Mapping[str, Any]: db_session = await self.get_session() try: dao = LocationDAO(db_session) return await dao.get_device_current_location(device_id=device_id, user_id=user_id) finally: await db_session.close() async def get_device_trajectory( self, *, device_id: str, user_id: int, start_at: datetime | None, end_at: datetime | None, limit: int, ) -> tuple[ParentDeviceAccess, list[Mapping[str, Any]]]: db_session = await self.get_session() try: dao = LocationDAO(db_session) return await dao.get_device_trajectory( device_id=device_id, user_id=user_id, start_at=start_at, end_at=end_at, limit=limit, ) finally: await db_session.close() async def insert_or_update(self, device_id: str, location: ChildLocationCurrent) -> None: db_session = await self.get_session() try: dao = LocationDAO(db_session) current_row = await dao.get_current_location_by_device_id(device_id=device_id) if current_row: await dao.update(device_id=device_id, latitude=location.lat, longitude=location.lng) # 更新成功加到历史记录表 else: await dao.insert(device_id=device_id, latitude=location.lat, longitude=location.lng) finally: await db_session.close() async def report_mqtt_device_location( self, *, device_id: str, latitude: float | None, longitude: float | None, coord_type: str | None = None, accuracy_m: int | None = None, altitude_m: float | None = None, speed_mps: float | None = None, heading_deg: int | None = None, source: int | None = None, battery_pct: int | None = None, device_time: datetime | None = None, ) -> Mapping[str, Any] | None: if latitude is None or longitude is None: return None @dataclass(frozen=True) class _MQTTLocationPayload: coord_type: str lat: float lng: float accuracy_m: int | None altitude_m: float | None speed_mps: float | None heading_deg: int | None source: int battery_pct: int | None device_time: datetime db_session = await self.get_session() try: dao = LocationDAO(db_session) binding_row = await dao.get_active_binding_by_device(device_id=device_id) if not binding_row or binding_row["child_id"] is None: return None payload = _MQTTLocationPayload( coord_type=(coord_type or "gcj02").strip() or "gcj02", lat=float(latitude), lng=float(longitude), accuracy_m=accuracy_m, altitude_m=altitude_m, speed_mps=speed_mps, heading_deg=heading_deg, source=source if source is not None else 0, battery_pct=battery_pct, device_time=device_time or datetime.now(), ) return await dao.report_device_location( device_id=device_id, child_id=int(binding_row["child_id"]), payload=payload, ) finally: await db_session.close() # 创建全局 LocationService 实例 location_service = LocationService()