85 lines
2.9 KiB
Python
85 lines
2.9 KiB
Python
|
|
import logging
|
|||
|
|
from django.db import transaction, IntegrityError
|
|||
|
|
from time import perf_counter
|
|||
|
|
from system.models import ShipLatestStatus
|
|||
|
|
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
def safe_bulk_upsert(ship_data_list):
|
|||
|
|
start_time = perf_counter()
|
|||
|
|
created = updated = 0
|
|||
|
|
try:
|
|||
|
|
# 验证数据格式
|
|||
|
|
valid_data = []
|
|||
|
|
for data in ship_data_list:
|
|||
|
|
if 'mmsi' not in data:
|
|||
|
|
logger.warning("Missing MMSI in data: %s", data)
|
|||
|
|
continue
|
|||
|
|
valid_data.append(data)
|
|||
|
|
|
|||
|
|
if not valid_data:
|
|||
|
|
return 0, 0
|
|||
|
|
|
|||
|
|
# 执行批量操作
|
|||
|
|
with transaction.atomic():
|
|||
|
|
current_mmsi = {item['mmsi'] for item in valid_data}
|
|||
|
|
existing_map = ShipLatestStatus.objects.in_bulk(
|
|||
|
|
current_mmsi, field_name='mmsi'
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
to_create = []
|
|||
|
|
to_update = []
|
|||
|
|
|
|||
|
|
for data in valid_data:
|
|||
|
|
mmsi = data['mmsi']
|
|||
|
|
if mmsi in existing_map:
|
|||
|
|
ship = existing_map[mmsi]
|
|||
|
|
for field, value in data.items():
|
|||
|
|
setattr(ship, field, value)
|
|||
|
|
to_update.append(ship)
|
|||
|
|
else:
|
|||
|
|
to_create.append(ShipLatestStatus(**data))
|
|||
|
|
|
|||
|
|
if to_create:
|
|||
|
|
created = len(ShipLatestStatus.objects.bulk_create(to_create))
|
|||
|
|
|
|||
|
|
if to_update:
|
|||
|
|
update_fields = [f.name for f in ShipLatestStatus._meta.fields
|
|||
|
|
if f.name not in ['id', 'mmsi']]
|
|||
|
|
ShipLatestStatus.objects.bulk_update(to_update, update_fields)
|
|||
|
|
updated = len(to_update)
|
|||
|
|
|
|||
|
|
except IntegrityError as e:
|
|||
|
|
logger.error("Integrity error in bulk upsert: %s", str(e))
|
|||
|
|
# 这里可以添加重试或更精细的错误处理
|
|||
|
|
raise
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.exception("Unexpected error in bulk upsert")
|
|||
|
|
raise
|
|||
|
|
|
|||
|
|
elapsed = (perf_counter() - start_time) * 1000
|
|||
|
|
logger.info(f"Processed {len(valid_data)} ships in {elapsed:.2f}ms: "
|
|||
|
|
f"{created} created, {updated} updated")
|
|||
|
|
|
|||
|
|
return created, updated
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
def get_nearby_ships(latitude: float, longitude: float, radius_nm: float = 20.0):
|
|||
|
|
# 将海里转换为公里(Haversine公式使用公里)
|
|||
|
|
radius_km = radius_nm * 1.852
|
|||
|
|
|
|||
|
|
# 使用原生SQL计算
|
|||
|
|
return ShipLatestStatus.objects.raw("""
|
|||
|
|
SELECT *,
|
|||
|
|
(6371 * acos(
|
|||
|
|
cos(radians(%s))
|
|||
|
|
* cos(radians(latitude))
|
|||
|
|
* cos(radians(longitude) - radians(%s))
|
|||
|
|
+ sin(radians(%s))
|
|||
|
|
* sin(radians(latitude))
|
|||
|
|
)) AS distance
|
|||
|
|
FROM ship_latest_status
|
|||
|
|
HAVING distance <= %s
|
|||
|
|
ORDER BY distance
|
|||
|
|
""", [latitude, longitude, latitude, radius_km])
|