后端更新
This commit is contained in:
@@ -1,97 +1,103 @@
|
||||
# consumer.py
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import traceback
|
||||
|
||||
import aio_pika
|
||||
|
||||
from config.rabbitMQ import *
|
||||
from models.AnalysisRequest import AnalysisRequest
|
||||
from models.SentinelRecordRequest import SentinelRecordRequest
|
||||
from service.vision import process_vehicle_animal_image
|
||||
from service.vision import (
|
||||
process_all_vehicle_animal_image,
|
||||
)
|
||||
|
||||
|
||||
async def mq_new_analysis_test(req: dict):
|
||||
"""将分析请求发送到 RabbitMQ 队列(异步版)"""
|
||||
connection = await aio_pika.connect_robust(
|
||||
f"amqp://{RABBIT_USER}:{RABBIT_PASSWORD}@{RABBIT_HOST}/{RABBIT_VHOST}"
|
||||
)
|
||||
class MQClient:
|
||||
"""RabbitMQ 单例客户端,支持生产和消费"""
|
||||
|
||||
async with connection:
|
||||
channel = await connection.channel()
|
||||
# 声明队列,确保队列存在
|
||||
queue = await channel.declare_queue(QUEUE_NAME, durable=True)
|
||||
_instance = None
|
||||
|
||||
message_body = json.dumps(req)
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
self._connection = None
|
||||
self._channel = None
|
||||
self._consumer_tasks = []
|
||||
|
||||
# ---------------- 连接初始化 ----------------
|
||||
async def init(self, prefetch_count: int = 10):
|
||||
"""启动时初始化连接和通道"""
|
||||
if self._connection is None:
|
||||
self._connection = await aio_pika.connect_robust(
|
||||
f"amqp://{RABBIT_USER}:{RABBIT_PASSWORD}@{RABBIT_HOST}/{SENTINEL_VHOST}"
|
||||
)
|
||||
self._channel = await self._connection.channel()
|
||||
await self._channel.set_qos(prefetch_count=prefetch_count)
|
||||
|
||||
# ---------------- 发布消息 ----------------
|
||||
async def publish(self, queue_name: str, message_body: str):
|
||||
"""向指定队列发送消息"""
|
||||
if self._channel is None:
|
||||
raise RuntimeError("MQClient 未初始化")
|
||||
# 队列幂等声明
|
||||
queue = await self._channel.declare_queue(queue_name, durable=True)
|
||||
message = aio_pika.Message(
|
||||
body=message_body.encode(),
|
||||
delivery_mode=aio_pika.DeliveryMode.PERSISTENT, # 持久化
|
||||
body=message_body.encode(), delivery_mode=aio_pika.DeliveryMode.PERSISTENT
|
||||
)
|
||||
await self._channel.default_exchange.publish(message, routing_key=queue_name)
|
||||
|
||||
async def send_all_analysis(self, req: SentinelRecordRequest):
|
||||
await self.publish(
|
||||
SENTINEL_ANALYSIS_ALL_QUEUE_NAME, json.dumps(req.model_dump())
|
||||
)
|
||||
|
||||
await channel.default_exchange.publish(message, routing_key=QUEUE_NAME)
|
||||
# ---------------- 消费消息 ----------------
|
||||
async def consume_queue(self, queue_name: str, process_func):
|
||||
"""
|
||||
持续消费队列
|
||||
process_func: async function 接收 dict 或 Request 对象
|
||||
"""
|
||||
if self._channel is None:
|
||||
raise RuntimeError("MQClient 未初始化")
|
||||
|
||||
|
||||
async def mq_pull_analysis_async_test():
|
||||
"""
|
||||
从队列拉取分析任务并处理
|
||||
process_func: 一个函数,接收 AnalysisRequest 对象处理分析逻辑
|
||||
"""
|
||||
connection = await aio_pika.connect_robust(
|
||||
f"amqp://{RABBIT_USER}:{RABBIT_PASSWORD}@{RABBIT_HOST}/{RABBIT_VHOST}"
|
||||
)
|
||||
async with connection:
|
||||
queue_name = QUEUE_NAME
|
||||
channel = await connection.channel()
|
||||
await channel.set_qos(prefetch_count=1)
|
||||
queue = await channel.declare_queue(queue_name, durable=True)
|
||||
queue = await self._channel.declare_queue(queue_name, durable=True)
|
||||
|
||||
async with queue.iterator() as queue_iter:
|
||||
async for message in queue_iter:
|
||||
async with message.process():
|
||||
data = json.loads(message.body)
|
||||
req = AnalysisRequest(**data)
|
||||
print(f"收到任务: {req}")
|
||||
await asyncio.sleep(5) # 模拟处理
|
||||
print(f"完成任务: {req}")
|
||||
try:
|
||||
body = message.body.decode()
|
||||
data = json.loads(body)
|
||||
await process_func(data)
|
||||
except Exception as e:
|
||||
print(f"[MQ Consume Error] {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
# ---------------- 启动全局分析消费者 ----------------
|
||||
async def start_all_consumer(self):
|
||||
async def _process(data: dict):
|
||||
req = SentinelRecordRequest(**data)
|
||||
await process_all_vehicle_animal_image(req)
|
||||
print(f"完成全局分析任务: {req}")
|
||||
|
||||
async def sentinel_new_analysis(req: SentinelRecordRequest):
|
||||
"""将分析请求发送到 RabbitMQ 队列(异步版)"""
|
||||
connection = await aio_pika.connect_robust(
|
||||
f"amqp://{RABBIT_USER}:{RABBIT_PASSWORD}@{RABBIT_HOST}/{SENTINEL_VHOST}"
|
||||
)
|
||||
|
||||
async with connection:
|
||||
channel = await connection.channel()
|
||||
# 声明队列,确保队列存在
|
||||
queue = await channel.declare_queue(QUEUE_NAME, durable=True)
|
||||
|
||||
message_body = json.dumps(req.model_dump())
|
||||
message = aio_pika.Message(
|
||||
body=message_body.encode(),
|
||||
delivery_mode=aio_pika.DeliveryMode.PERSISTENT, # 持久化
|
||||
task = asyncio.create_task(
|
||||
self.consume_queue(SENTINEL_ANALYSIS_ALL_QUEUE_NAME, _process)
|
||||
)
|
||||
self._consumer_tasks.append(task)
|
||||
|
||||
await channel.default_exchange.publish(message, routing_key=QUEUE_NAME)
|
||||
# ---------------- 关闭连接 ----------------
|
||||
async def close(self):
|
||||
for task in self._consumer_tasks:
|
||||
task.cancel()
|
||||
if self._channel:
|
||||
await self._channel.close()
|
||||
if self._connection:
|
||||
await self._connection.close()
|
||||
|
||||
|
||||
async def sentinel_pull_analysis_async():
|
||||
"""
|
||||
从队列拉取分析任务并处理
|
||||
process_func: 一个函数,接收 AnalysisRequest 对象处理分析逻辑
|
||||
"""
|
||||
connection = await aio_pika.connect_robust(
|
||||
f"amqp://{RABBIT_USER}:{RABBIT_PASSWORD}@{RABBIT_HOST}/{SENTINEL_VHOST}"
|
||||
)
|
||||
async with connection:
|
||||
queue_name = QUEUE_NAME
|
||||
channel = await connection.channel()
|
||||
await channel.set_qos(prefetch_count=1)
|
||||
queue = await channel.declare_queue(queue_name, durable=True)
|
||||
|
||||
async with queue.iterator() as queue_iter:
|
||||
async for message in queue_iter:
|
||||
async with message.process():
|
||||
data = json.loads(message.body)
|
||||
req = SentinelRecordRequest(**data)
|
||||
await process_vehicle_animal_image(req) # 处理
|
||||
print(f"完成任务: {req}")
|
||||
# ---------------- 全局单例 ----------------
|
||||
mq_client = MQClient()
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from uuid import UUID
|
||||
|
||||
import config.minIO as minIO
|
||||
import db.postgres as pg
|
||||
from agent.licenseImageAgent import get_license_response
|
||||
from agent.vehicleImageAgent import get_vehicle_response
|
||||
from ai.plate.my_plate import recognizer
|
||||
from config.minIO import minio_client, get_temp_url
|
||||
from config.yolo import YOLOSingleton
|
||||
from db.postgres import (
|
||||
get_dept_id_by_iot_user_name,
|
||||
get_dept_ids_by_dept_id,
|
||||
)
|
||||
from db.postgres.sentinel import update_sentinel_record, get_sentinel_record_by_id
|
||||
from db.postgres.sentinel import (
|
||||
get_sentinel_record_by_id,
|
||||
saveSentinelRecord,
|
||||
)
|
||||
from llm.ticketLLM import *
|
||||
from llm.ticketLLMv2 import get_ticket_response_v2
|
||||
from models.SentinelRecordRequest import SentinelRecordRequest
|
||||
from routers.WS import ws_manager
|
||||
from utils import validate_plate
|
||||
|
||||
|
||||
def process_ticket_image(
|
||||
@@ -189,35 +195,97 @@ def process_silkworm_cocoon_image(
|
||||
}
|
||||
|
||||
|
||||
async def process_vehicle_animal_image(
|
||||
# 处理车牌照片
|
||||
async def process_all_vehicle_animal_image(
|
||||
data: SentinelRecordRequest,
|
||||
):
|
||||
# 通过设备id获得组织id
|
||||
dept_id = get_dept_id_by_iot_user_name(data.DeviceId)
|
||||
oss_url = minIO.get_temp_url(
|
||||
"sentinel", "vehicle_image_side/" + data.vehicleImageSide
|
||||
)
|
||||
|
||||
# 得到动物类型
|
||||
oss_url = minIO.get_temp_url("sentinel", "vehicle_image/" + data.VehicleImage)
|
||||
# LLM得到车身信息
|
||||
analysis_result = await get_vehicle_response(oss_url)
|
||||
livestock_type = analysis_result.get("livestock_type", "")
|
||||
remark = analysis_result.get("remark", "")
|
||||
# 保存到数据库
|
||||
update_sentinel_record(data.Id, livestock_type, remark, dept_id)
|
||||
# 可以通知的部门ids
|
||||
available_departments = get_dept_ids_by_dept_id(dept_id)
|
||||
have_animal = analysis_result.get("have_animal", False)
|
||||
|
||||
# 通知控制界面
|
||||
await ws_manager.noticeSentinel(
|
||||
{
|
||||
"content": f"载有{livestock_type}的车辆即将进入关卡,请准备检查",
|
||||
"type": "vehicle_alert",
|
||||
},
|
||||
available_departments,
|
||||
)
|
||||
# 通知大屏界面
|
||||
await ws_manager.noticeSentinelMonitorStatus(
|
||||
{
|
||||
"content": get_sentinel_record_by_id(data.Id),
|
||||
"type": "vehicle_alert",
|
||||
},
|
||||
available_departments,
|
||||
)
|
||||
if not have_animal:
|
||||
minIO.delete_file("sentinel", "vehicle_image_side/" + data.vehicleImageSide)
|
||||
minIO.delete_file("sentinel", "vehicle_image_front/" + data.vehicleImageFront)
|
||||
else:
|
||||
# 通过设备id获得组织id
|
||||
dept_id = get_dept_id_by_iot_user_name(data.DeviceId)
|
||||
|
||||
# 处理汽车正面照--------------------------
|
||||
|
||||
# 从OSS下载
|
||||
oss_url = minIO.get_temp_url(
|
||||
"sentinel", "vehicle_image_front/" + data.vehicleImageFront
|
||||
)
|
||||
|
||||
# 获取系统临时目录(自动兼容 Windows / Linux)
|
||||
tmp_dir = Path(tempfile.gettempdir())
|
||||
tmp_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
tmp_path = tmp_dir / data.vehicleImageFront
|
||||
|
||||
# 下载图片到 tmp_path
|
||||
response = requests.get(oss_url, stream=True)
|
||||
if response.status_code == 200:
|
||||
with open(tmp_path, "wb") as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
f.write(chunk)
|
||||
else:
|
||||
raise Exception(f"下载失败: {oss_url}, status_code={response.status_code}")
|
||||
|
||||
# 调用识别
|
||||
results = recognizer.analyze_image(str(tmp_path))
|
||||
# 车牌号一次识别
|
||||
license_plate = results[0].plate_no if results else ""
|
||||
# 车牌号二次校准
|
||||
license_plate = validate_plate(license_plate)
|
||||
license_plate_color_str = results[0].plate_color if results else ""
|
||||
color_map = {
|
||||
"蓝色": 0,
|
||||
"黄色": 1,
|
||||
"绿色": 2,
|
||||
"黑色": 3,
|
||||
"白色": 4,
|
||||
}
|
||||
|
||||
license_plate_color = color_map.get(license_plate_color_str, 0)
|
||||
license_plate_image = data.vehicleImageFront
|
||||
# 保存到数据库
|
||||
saveSentinelRecord(
|
||||
data.Id,
|
||||
data.VehicleType,
|
||||
data.vehicleImageSide,
|
||||
livestock_type,
|
||||
remark,
|
||||
dept_id,
|
||||
license_plate,
|
||||
license_plate_image,
|
||||
license_plate_color,
|
||||
)
|
||||
# 识别完成后删除临时文件
|
||||
os.remove(tmp_path)
|
||||
|
||||
# 可以通知的部门ids
|
||||
available_departments = get_dept_ids_by_dept_id(dept_id)
|
||||
# 通知控制界面
|
||||
await ws_manager.noticeSentinel(
|
||||
{
|
||||
"content": f"车辆即将进入关卡,请准备检查",
|
||||
"license_plate": license_plate,
|
||||
"type": "vehicle_alert",
|
||||
},
|
||||
available_departments,
|
||||
)
|
||||
# 通知大屏界面
|
||||
await ws_manager.noticeSentinelMonitorStatus(
|
||||
{
|
||||
"content": get_sentinel_record_by_id(data.Id),
|
||||
"type": "vehicle_alert",
|
||||
},
|
||||
available_departments,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user