服务接口初步完成
This commit is contained in:
parent
f9d35b1ba8
commit
57578f94dc
5
app/api/v1/api.py
Normal file
5
app/api/v1/api.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
from app.api.v1.endpoints import events
|
||||||
|
|
||||||
|
api_router = APIRouter()
|
||||||
|
api_router.include_router(events.router, prefix="/api/v1", tags=["events"])
|
||||||
79
app/api/v1/endpoints/events.py
Normal file
79
app/api/v1/endpoints/events.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from app.core.database import get_db
|
||||||
|
from app.crud.event import event
|
||||||
|
from app.schemas.event import EventList, EventDetail, EventUpdate, EventQuery
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.get("/events", response_model=List[EventList])
|
||||||
|
async def get_events(
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
start_time: Optional[str] = None,
|
||||||
|
end_time: Optional[str] = None,
|
||||||
|
etypeName: Optional[str] = None,
|
||||||
|
area: Optional[str] = None,
|
||||||
|
skip: int = Query(0, ge=0),
|
||||||
|
limit: int = Query(100, ge=1, le=1000)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
获取事件列表
|
||||||
|
- 支持时间范围查询
|
||||||
|
- 支持事件类型和区域筛选
|
||||||
|
- 支持分页
|
||||||
|
"""
|
||||||
|
query = EventQuery(
|
||||||
|
start_time=start_time,
|
||||||
|
end_time=end_time,
|
||||||
|
etypeName=etypeName,
|
||||||
|
area=area,
|
||||||
|
skip=skip,
|
||||||
|
limit=limit
|
||||||
|
)
|
||||||
|
events = await event.get_multi_with_query(db, query=query)
|
||||||
|
return [EventList.model_validate(event) for event in events]
|
||||||
|
|
||||||
|
@router.get("/events/{event_id}", response_model=EventDetail)
|
||||||
|
async def get_event(
|
||||||
|
event_id: str,
|
||||||
|
db: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
获取事件详情
|
||||||
|
"""
|
||||||
|
event_obj = await event.get_by_id(db, event_id=event_id)
|
||||||
|
if not event_obj:
|
||||||
|
raise HTTPException(status_code=404, detail="事件不存在")
|
||||||
|
return EventDetail.model_validate(event_obj)
|
||||||
|
|
||||||
|
@router.put("/events/{event_id}", response_model=EventDetail)
|
||||||
|
async def update_event(
|
||||||
|
event_id: str,
|
||||||
|
event_in: EventUpdate,
|
||||||
|
db: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
更新事件信息
|
||||||
|
"""
|
||||||
|
event_obj = await event.update_event(
|
||||||
|
db,
|
||||||
|
event_id=event_id,
|
||||||
|
obj_in=event_in
|
||||||
|
)
|
||||||
|
if not event_obj:
|
||||||
|
raise HTTPException(status_code=404, detail="事件不存在")
|
||||||
|
return EventDetail.model_validate(event_obj)
|
||||||
|
|
||||||
|
@router.delete("/events/{event_id}", response_model=EventDetail)
|
||||||
|
async def delete_event(
|
||||||
|
event_id: str,
|
||||||
|
db: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
删除事件
|
||||||
|
"""
|
||||||
|
event_obj = await event.delete_event(db, event_id=event_id)
|
||||||
|
if not event_obj:
|
||||||
|
raise HTTPException(status_code=404, detail="事件不存在")
|
||||||
|
return EventDetail.model_validate(event_obj)
|
||||||
93
app/crud/event.py
Normal file
93
app/crud/event.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
from typing import List, Optional, Dict, Any
|
||||||
|
from sqlalchemy import select, and_, or_
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy.orm import selectinload
|
||||||
|
from app.crud.base import CRUDBase
|
||||||
|
from app.models.models import Event, Image, Temperature
|
||||||
|
from app.schemas.event import EventUpdate, EventQuery
|
||||||
|
|
||||||
|
class CRUDEvent(CRUDBase[Event, EventUpdate, EventUpdate]):
|
||||||
|
async def get_by_id(self, db: AsyncSession, *, event_id: str) -> Optional[Event]:
|
||||||
|
"""根据ID获取事件"""
|
||||||
|
query = (
|
||||||
|
select(Event)
|
||||||
|
.options(
|
||||||
|
selectinload(Event.images),
|
||||||
|
selectinload(Event.temperatures)
|
||||||
|
)
|
||||||
|
.where(Event.eventId == event_id)
|
||||||
|
)
|
||||||
|
result = await db.execute(query)
|
||||||
|
return result.scalar_one_or_none()
|
||||||
|
|
||||||
|
async def get_multi_with_query(
|
||||||
|
self,
|
||||||
|
db: AsyncSession,
|
||||||
|
*,
|
||||||
|
query: EventQuery
|
||||||
|
) -> List[Event]:
|
||||||
|
"""根据查询条件获取事件列表"""
|
||||||
|
conditions = []
|
||||||
|
|
||||||
|
if query.start_time:
|
||||||
|
conditions.append(Event.insDate >= query.start_time)
|
||||||
|
if query.end_time:
|
||||||
|
conditions.append(Event.insDate <= query.end_time)
|
||||||
|
if query.etypeName:
|
||||||
|
conditions.append(Event.etypeName == query.etypeName)
|
||||||
|
if query.area:
|
||||||
|
conditions.append(Event.area == query.area)
|
||||||
|
|
||||||
|
query_stmt = (
|
||||||
|
select(Event)
|
||||||
|
.options(
|
||||||
|
selectinload(Event.images),
|
||||||
|
selectinload(Event.temperatures)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if conditions:
|
||||||
|
query_stmt = query_stmt.where(and_(*conditions))
|
||||||
|
|
||||||
|
query_stmt = query_stmt.offset(query.skip).limit(query.limit)
|
||||||
|
|
||||||
|
result = await db.execute(query_stmt)
|
||||||
|
return result.scalars().all()
|
||||||
|
|
||||||
|
async def update_event(
|
||||||
|
self,
|
||||||
|
db: AsyncSession,
|
||||||
|
*,
|
||||||
|
event_id: str,
|
||||||
|
obj_in: EventUpdate
|
||||||
|
) -> Optional[Event]:
|
||||||
|
"""更新事件信息"""
|
||||||
|
event = await self.get_by_id(db, event_id=event_id)
|
||||||
|
if not event:
|
||||||
|
return None
|
||||||
|
|
||||||
|
update_data = obj_in.model_dump()
|
||||||
|
for field, value in update_data.items():
|
||||||
|
setattr(event, field, value)
|
||||||
|
|
||||||
|
# db.add(event)
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(event)
|
||||||
|
return event
|
||||||
|
|
||||||
|
async def delete_event(
|
||||||
|
self,
|
||||||
|
db: AsyncSession,
|
||||||
|
*,
|
||||||
|
event_id: str
|
||||||
|
) -> Optional[Event]:
|
||||||
|
"""删除事件"""
|
||||||
|
event = await self.get_by_id(db, event_id=event_id)
|
||||||
|
if not event:
|
||||||
|
return None
|
||||||
|
|
||||||
|
await db.delete(event)
|
||||||
|
await db.commit()
|
||||||
|
return event
|
||||||
|
|
||||||
|
event = CRUDEvent(Event)
|
||||||
26
app/main.py
Normal file
26
app/main.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from app.api.v1.api import api_router
|
||||||
|
from app.core.config import settings
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title="温度数据采集系统",
|
||||||
|
description="温度数据采集系统API接口",
|
||||||
|
version="1.0.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 配置CORS
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"], # 在生产环境中应该设置具体的域名
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# 注册路由
|
||||||
|
app.include_router(api_router)
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
async def root():
|
||||||
|
return {"message": "温度数据采集系统API服务正在运行"}
|
||||||
@ -90,6 +90,7 @@ class Temperature(Base):
|
|||||||
eventId = Column(String(50), ForeignKey('event.eventId'), nullable=False, comment='关联事件ID')
|
eventId = Column(String(50), ForeignKey('event.eventId'), nullable=False, comment='关联事件ID')
|
||||||
imageId = Column(BigInteger, ForeignKey('image.imageId'), nullable=False, comment='关联图片ID')
|
imageId = Column(BigInteger, ForeignKey('image.imageId'), nullable=False, comment='关联图片ID')
|
||||||
temperature = Column(String(20), nullable=False, comment='温度值')
|
temperature = Column(String(20), nullable=False, comment='温度值')
|
||||||
|
# status = Column(String(2), comment='温度是否正常')
|
||||||
confidence = Column(String(20), nullable=False, comment='识别置信度')
|
confidence = Column(String(20), nullable=False, comment='识别置信度')
|
||||||
createTime = Column(DateTime, default=datetime.now, nullable=False, comment='创建时间')
|
createTime = Column(DateTime, default=datetime.now, nullable=False, comment='创建时间')
|
||||||
|
|
||||||
|
|||||||
102
app/schemas/event.py
Normal file
102
app/schemas/event.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import List, Optional
|
||||||
|
from pydantic import BaseModel, ConfigDict
|
||||||
|
|
||||||
|
class ImageBase(BaseModel):
|
||||||
|
imageUrl: str
|
||||||
|
localPath: Optional[str] = None
|
||||||
|
|
||||||
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
class TemperatureBase(BaseModel):
|
||||||
|
temperature: str
|
||||||
|
confidence: str
|
||||||
|
createTime: datetime
|
||||||
|
|
||||||
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
class EventBase(BaseModel):
|
||||||
|
eventId: str
|
||||||
|
tenantInfoId: Optional[str] = None
|
||||||
|
reportEventId: Optional[str] = None
|
||||||
|
number: Optional[str] = None
|
||||||
|
name: Optional[str] = None
|
||||||
|
etypeName: Optional[str] = None
|
||||||
|
insDate: Optional[datetime] = None
|
||||||
|
|
||||||
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
class EventList(EventBase):
|
||||||
|
images: List[ImageBase] = []
|
||||||
|
temperatures: List[TemperatureBase] = []
|
||||||
|
|
||||||
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
class EventDetail(EventBase):
|
||||||
|
eclassify: Optional[str] = None
|
||||||
|
operationType: Optional[str] = None
|
||||||
|
etype: Optional[str] = None
|
||||||
|
enTypeName: Optional[str] = None
|
||||||
|
hkTypeName: Optional[str] = None
|
||||||
|
reportStatus: Optional[str] = None
|
||||||
|
results: Optional[str] = None
|
||||||
|
insDateShow: Optional[datetime] = None
|
||||||
|
updDate: Optional[datetime] = None
|
||||||
|
updDateShow: Optional[datetime] = None
|
||||||
|
fileType: Optional[str] = None
|
||||||
|
area: Optional[str] = None
|
||||||
|
floor: Optional[str] = None
|
||||||
|
map: Optional[str] = None
|
||||||
|
staffId: Optional[str] = None
|
||||||
|
targetUserId: Optional[str] = None
|
||||||
|
position: Optional[str] = None
|
||||||
|
actualStaffName: Optional[str] = None
|
||||||
|
targetStaffName: Optional[str] = None
|
||||||
|
routeName: Optional[str] = None
|
||||||
|
phoneAddress: Optional[str] = None
|
||||||
|
width: Optional[str] = None
|
||||||
|
height: Optional[str] = None
|
||||||
|
resolution: Optional[str] = None
|
||||||
|
originX: Optional[str] = None
|
||||||
|
originY: Optional[str] = None
|
||||||
|
robotType: Optional[str] = None
|
||||||
|
eventFloor: Optional[str] = None
|
||||||
|
floorName: Optional[str] = None
|
||||||
|
coordId: Optional[str] = None
|
||||||
|
coord: Optional[str] = None
|
||||||
|
coordName: Optional[str] = None
|
||||||
|
positonName: Optional[str] = None
|
||||||
|
processingRemark: Optional[str] = None
|
||||||
|
carId: Optional[str] = None
|
||||||
|
parkingSpaceType: Optional[str] = None
|
||||||
|
parkingSpaceNumber: Optional[str] = None
|
||||||
|
carNumber: Optional[str] = None
|
||||||
|
eno: Optional[str] = None
|
||||||
|
instrument: Optional[str] = None
|
||||||
|
evideo: Optional[str] = None
|
||||||
|
createTime: Optional[datetime] = None
|
||||||
|
updateTime: Optional[datetime] = None
|
||||||
|
images: List[ImageBase] = []
|
||||||
|
temperatures: List[TemperatureBase] = []
|
||||||
|
|
||||||
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
class EventUpdate(BaseModel):
|
||||||
|
number: Optional[str] = None
|
||||||
|
name: Optional[str] = None
|
||||||
|
etypeName: Optional[str] = None
|
||||||
|
area: Optional[str] = None
|
||||||
|
position: Optional[str] = None
|
||||||
|
processingRemark: Optional[str] = None
|
||||||
|
|
||||||
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
class EventQuery(BaseModel):
|
||||||
|
start_time: Optional[datetime] = None
|
||||||
|
end_time: Optional[datetime] = None
|
||||||
|
etypeName: Optional[str] = None
|
||||||
|
area: Optional[str] = None
|
||||||
|
skip: int = 0
|
||||||
|
limit: int = 100
|
||||||
|
|
||||||
|
model_config = ConfigDict(from_attributes=True)
|
||||||
@ -45,62 +45,40 @@
|
|||||||
|
|
||||||
## 3. 详细设计
|
## 3. 详细设计
|
||||||
|
|
||||||
### 3.1 数据采集模块
|
### 3.1 数据同步模块
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class DataCollector:
|
class EventSyncService:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.token_manager = TokenManager()
|
self.kangda = Kangda()
|
||||||
self.http_client = AsyncHTTPClient()
|
self.ocr = BadiduOcr()
|
||||||
|
|
||||||
|
async def sync_events(self):
|
||||||
|
"""同步事件数据"""
|
||||||
|
print("开始同步事件数据...")
|
||||||
|
|
||||||
async def collect_events(self):
|
|
||||||
# 获取token
|
|
||||||
token = await self.token_manager.get_token()
|
|
||||||
# 获取事件列表
|
# 获取事件列表
|
||||||
events = await self.fetch_events(token)
|
event_list = self.kangda.get_event_list()
|
||||||
# 获取事件详情
|
if not event_list:
|
||||||
for event in events:
|
print("获取事件列表失败")
|
||||||
await self.process_event(event, token)
|
return
|
||||||
|
|
||||||
|
# 获取新事件列表
|
||||||
|
new_events = await self._get_new_events(event_list)
|
||||||
|
if not new_events:
|
||||||
|
print("没有新事件需要同步")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"发现 {len(new_events)} 个新事件")
|
||||||
|
|
||||||
|
# 获取事件详情并更新
|
||||||
|
await self._update_event_details(new_events)
|
||||||
|
|
||||||
|
print("事件同步完成
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3.2 OCR处理模块
|
|
||||||
|
|
||||||
```python
|
### 3.2 API接口模块
|
||||||
class OCRProcessor:
|
|
||||||
def __init__(self):
|
|
||||||
self.ocr = PaddleOCR()
|
|
||||||
|
|
||||||
async def process_image(self, image_path: str) -> Dict:
|
|
||||||
# 图片预处理
|
|
||||||
image = self.preprocess_image(image_path)
|
|
||||||
# OCR识别
|
|
||||||
result = self.ocr.ocr(image)
|
|
||||||
# 提取温度数据
|
|
||||||
temperature = self.extract_temperature(result)
|
|
||||||
return temperature
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.3 数据存储模块
|
|
||||||
|
|
||||||
```python
|
|
||||||
class DatabaseManager:
|
|
||||||
def __init__(self):
|
|
||||||
self.engine = create_engine(
|
|
||||||
'mysql+aiomysql://user:password@localhost:3306/temp_db',
|
|
||||||
pool_size=5,
|
|
||||||
max_overflow=10,
|
|
||||||
pool_timeout=30,
|
|
||||||
pool_recycle=1800
|
|
||||||
)
|
|
||||||
self.session = SessionLocal()
|
|
||||||
|
|
||||||
async def save_event(self, event_data: Dict):
|
|
||||||
event = Event(**event_data)
|
|
||||||
self.session.add(event)
|
|
||||||
await self.session.commit()
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.4 API接口模块
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@router.get("/temperatures")
|
@router.get("/temperatures")
|
||||||
@ -113,7 +91,6 @@ async def get_temperatures(
|
|||||||
db, start_time, end_time
|
db, start_time, end_time
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 4. 数据库设计
|
## 4. 数据库设计
|
||||||
|
|
||||||
### 4.1 表结构
|
### 4.1 表结构
|
||||||
@ -136,7 +113,6 @@ ALTER TABLE image ADD INDEX idx_image_event_id (event_id);
|
|||||||
ALTER TABLE process_log ADD INDEX idx_log_event_id (event_id);
|
ALTER TABLE process_log ADD INDEX idx_log_event_id (event_id);
|
||||||
ALTER TABLE process_log ADD INDEX idx_log_create_time (create_time);
|
ALTER TABLE process_log ADD INDEX idx_log_create_time (create_time);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4.3 数据库配置
|
### 4.3 数据库配置
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
@ -152,7 +128,6 @@ max_overflow = 10
|
|||||||
pool_timeout = 30
|
pool_timeout = 30
|
||||||
pool_recycle = 1800
|
pool_recycle = 1800
|
||||||
```
|
```
|
||||||
|
|
||||||
## 5. 接口设计
|
## 5. 接口设计
|
||||||
|
|
||||||
### 5.1 内部接口
|
### 5.1 内部接口
|
||||||
@ -309,7 +284,6 @@ pool_recycle = 1800
|
|||||||
| |
|
| |
|
||||||
[Redis集群] [文件存储]
|
[Redis集群] [文件存储]
|
||||||
```
|
```
|
||||||
|
|
||||||
### 9.3 MySQL配置建议
|
### 9.3 MySQL配置建议
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
@ -337,7 +311,6 @@ server-id = 1
|
|||||||
log-bin = mysql-bin
|
log-bin = mysql-bin
|
||||||
binlog_format = ROW
|
binlog_format = ROW
|
||||||
```
|
```
|
||||||
|
|
||||||
## 10. 测试方案
|
## 10. 测试方案
|
||||||
|
|
||||||
### 10.1 单元测试
|
### 10.1 单元测试
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
# 需求:
|
# 需求:
|
||||||
|
|
||||||
- 设计一个Python后台服务程序,用于自动化采集和处理温度数据
|
- 设计一个Python后台服务程序,用于自动化采集和处理温度数据
|
||||||
- 通过WebSocket接收外部系统的消息触发
|
- 通过WebSocket接收外部系统的消息触发
|
||||||
- 支持事件驱动的数据采集和处理流程
|
- 支持事件驱动的数据采集和处理流程
|
||||||
|
|
||||||
# 功能:
|
# 功能:
|
||||||
|
|
||||||
- WebSocket服务
|
- WebSocket服务
|
||||||
- 接收外部系统的消息
|
- 接收外部系统的消息
|
||||||
- 处理消息并触发相应的数据采集流程
|
- 处理消息并触发相应的数据采集流程
|
||||||
@ -18,6 +20,7 @@
|
|||||||
- 支持事件的搜索和过滤
|
- 支持事件的搜索和过滤
|
||||||
|
|
||||||
# 系统架构:
|
# 系统架构:
|
||||||
|
|
||||||
- 采用分层架构设计
|
- 采用分层架构设计
|
||||||
- 数据采集层:负责与第三方API交互
|
- 数据采集层:负责与第三方API交互
|
||||||
- 业务处理层:负责数据处理和OCR识别
|
- 业务处理层:负责数据处理和OCR识别
|
||||||
@ -25,6 +28,7 @@
|
|||||||
- API接口层:提供对外服务接口
|
- API接口层:提供对外服务接口
|
||||||
|
|
||||||
# 技术选型:
|
# 技术选型:
|
||||||
|
|
||||||
- 开发语言:Python 3.8+
|
- 开发语言:Python 3.8+
|
||||||
- Web框架:FastAPI
|
- Web框架:FastAPI
|
||||||
- 数据库:MySQL 8.0+
|
- 数据库:MySQL 8.0+
|
||||||
@ -36,160 +40,91 @@
|
|||||||
- 日志:loguru
|
- 日志:loguru
|
||||||
|
|
||||||
# 具体第三方接口:
|
# 具体第三方接口:
|
||||||
- 获取token和refresh接口
|
|
||||||
- url:
|
|
||||||
- http://erpapi.concoai.com/basis-api/user/login
|
|
||||||
- 传递参数:
|
|
||||||
- Query参数
|
|
||||||
- cookie_flag: Optional[str] = None
|
|
||||||
- device_id: Optional[str] = None
|
|
||||||
- is_login: Optional[str] = None
|
|
||||||
- lang: Optional[str] = None
|
|
||||||
- password: Optional[str] = None
|
|
||||||
- username: Optional[str] = None
|
|
||||||
- 响应参数: 重点保存 data中的token和refreshtoken
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": "2000",
|
|
||||||
"message": "成功",
|
|
||||||
"total": null,
|
|
||||||
"data": {
|
|
||||||
"token": "xxx",
|
|
||||||
"refreshToken": "xxx",
|
|
||||||
"isLogin": null,
|
|
||||||
"username": null,
|
|
||||||
"password": null,
|
|
||||||
"rolePath": "/index/staff-page"
|
|
||||||
},
|
|
||||||
"rows": null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
- 获取列表页接口
|
|
||||||
- url:
|
|
||||||
- http://erpapi.concoai.com/robot/event/page
|
|
||||||
- 传递参数:
|
|
||||||
- Header 参数
|
|
||||||
- token
|
|
||||||
- refreshToken
|
|
||||||
- Body 参数
|
|
||||||
```json
|
|
||||||
{"pageNo": 1, "pageSize": 10000}
|
|
||||||
```
|
|
||||||
- 响应参数: 获取rows中 的eventId ,insDate, insDateShow
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": "2000",
|
|
||||||
"message": "成功",
|
|
||||||
"total": 1,
|
|
||||||
"data": null,
|
|
||||||
"rows": [
|
|
||||||
{
|
|
||||||
"eventId": "a462a29d7481495380cd47035b19edc0",
|
|
||||||
"tenantInfoId": "4fff5d4bcc4b4239941ff077a0da8958",
|
|
||||||
"reportEventId": null,
|
|
||||||
"number": "ROB23100098",
|
|
||||||
"name": "X32305000019",
|
|
||||||
"eclassify": "2",
|
|
||||||
"operationType": "2",
|
|
||||||
"etype": "E000007",
|
|
||||||
"etypeName": "日常巡检",
|
|
||||||
"enTypeName": "Routine patrol",
|
|
||||||
"hkTypeName": "日常巡檢\r",
|
|
||||||
"reportStatus": "0",
|
|
||||||
"results": "2",
|
|
||||||
"insDate": "2025-05-06T16:44:31",
|
|
||||||
"insDateShow": "2025-05-06 16:44:31",
|
|
||||||
"updDate": null,
|
|
||||||
"updDateShow": null,
|
|
||||||
"fileType": null,
|
|
||||||
"area": null,
|
|
||||||
"floor": null,
|
|
||||||
"map": null,
|
|
||||||
"staffId": null,
|
|
||||||
"targetUserId": null,
|
|
||||||
"position": null,
|
|
||||||
"actualStaffName": null,
|
|
||||||
"targetStaffName": "员工1",
|
|
||||||
"routeName": "test",
|
|
||||||
"phoneAddress": null,
|
|
||||||
"width": null,
|
|
||||||
"height": null,
|
|
||||||
"resolution": null,
|
|
||||||
"originX": null,
|
|
||||||
"originY": null,
|
|
||||||
"imgList": null,
|
|
||||||
"robotType": "08",
|
|
||||||
"eventFloor": null,
|
|
||||||
"floorName": null,
|
|
||||||
"coordId": null,
|
|
||||||
"coord": null,
|
|
||||||
"coordName": null,
|
|
||||||
"positonName": "A3",
|
|
||||||
"processingRemark": null,
|
|
||||||
"carId": null,
|
|
||||||
"parkingSpaceType": null,
|
|
||||||
"parkingSpaceNumber": null,
|
|
||||||
"carNumber": null,
|
|
||||||
"eno": null,
|
|
||||||
"instrument": null,
|
|
||||||
"evideo": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- 获取事件详情接口
|
- 获取token和refresh接口
|
||||||
- url:
|
|
||||||
- http://erpapi.concoai.com/robot/event/{eventId}
|
- url:
|
||||||
- 传递参数:
|
- http://erpapi.concoai.com/basis-api/user/login
|
||||||
- 路径变量:
|
- 传递参数:
|
||||||
- eventId
|
- Query参数
|
||||||
- 请求头参数:
|
- cookie_flag: Optional[str] = None
|
||||||
- token:
|
- device_id: Optional[str] = None
|
||||||
- refreshToken
|
- is_login: Optional[str] = None
|
||||||
- 响应参数: 主要获取imgList中的图片url.
|
- lang: Optional[str] = None
|
||||||
```json
|
- password: Optional[str] = None
|
||||||
{
|
- username: Optional[str] = None
|
||||||
"code": "2000",
|
- 响应参数: 重点保存 data中的token和refreshtoken
|
||||||
"message": "成功",
|
```json
|
||||||
"total": null,
|
{
|
||||||
"data": {
|
"code": "2000",
|
||||||
|
"message": "成功",
|
||||||
|
"total": null,
|
||||||
|
"data": {
|
||||||
|
"token": "xxx",
|
||||||
|
"refreshToken": "xxx",
|
||||||
|
"isLogin": null,
|
||||||
|
"username": null,
|
||||||
|
"password": null,
|
||||||
|
"rolePath": "/index/staff-page"
|
||||||
|
},
|
||||||
|
"rows": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- 获取列表页接口
|
||||||
|
|
||||||
|
- url:
|
||||||
|
- http://erpapi.concoai.com/robot/event/page
|
||||||
|
- 传递参数:
|
||||||
|
- Header 参数
|
||||||
|
- token
|
||||||
|
- refreshToken
|
||||||
|
- Body 参数
|
||||||
|
```json
|
||||||
|
{"pageNo": 1, "pageSize": 10000}
|
||||||
|
```
|
||||||
|
- 响应参数: 获取rows中 的eventId ,insDate, insDateShow
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "2000",
|
||||||
|
"message": "成功",
|
||||||
|
"total": 1,
|
||||||
|
"data": null,
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
"eventId": "a462a29d7481495380cd47035b19edc0",
|
"eventId": "a462a29d7481495380cd47035b19edc0",
|
||||||
"tenantInfoId": "4fff5d4bcc4b4239941ff077a0da8958",
|
"tenantInfoId": "4fff5d4bcc4b4239941ff077a0da8958",
|
||||||
"reportEventId": "bb96f28e14d944c79f44321edca4395f",
|
"reportEventId": null,
|
||||||
"number": "ROB23100098",
|
"number": "ROB23100098",
|
||||||
"name": "X32305000019",
|
"name": "X32305000019",
|
||||||
"eclassify": "2",
|
"eclassify": "2",
|
||||||
"operationType": "2",
|
"operationType": "2",
|
||||||
"etype": "E000007",
|
"etype": "E000007",
|
||||||
"etypeName": "日常巡检",
|
"etypeName": "日常巡检",
|
||||||
"enTypeName": null,
|
"enTypeName": "Routine patrol",
|
||||||
"hkTypeName": null,
|
"hkTypeName": "日常巡檢\r",
|
||||||
"reportStatus": "0",
|
"reportStatus": "0",
|
||||||
"results": "2",
|
"results": "2",
|
||||||
"insDate": null,
|
"insDate": "2025-05-06T16:44:31",
|
||||||
"insDateShow": "2025-05-06 16:44:31",
|
"insDateShow": "2025-05-06 16:44:31",
|
||||||
"updDate": null,
|
"updDate": null,
|
||||||
"updDateShow": null,
|
"updDateShow": null,
|
||||||
"fileType": "2",
|
"fileType": null,
|
||||||
"area": "区域1",
|
"area": null,
|
||||||
"floor": "",
|
"floor": null,
|
||||||
"map": null,
|
"map": null,
|
||||||
"staffId": null,
|
"staffId": null,
|
||||||
"targetUserId": null,
|
"targetUserId": null,
|
||||||
"position": "75.104378,90.402679",
|
"position": null,
|
||||||
"actualStaffName": null,
|
"actualStaffName": null,
|
||||||
"targetStaffName": "员工1",
|
"targetStaffName": "员工1",
|
||||||
"routeName": "test",
|
"routeName": "test",
|
||||||
"phoneAddress": "http://file.prod.concoai.com/image/4fff5d4bcc4b4239941ff077a0da8958/4f60fbd391e98327f6ee13df1455f23e.jpg",
|
"phoneAddress": null,
|
||||||
"width": "3994.0",
|
"width": null,
|
||||||
"height": "4816.0",
|
"height": null,
|
||||||
"resolution": "0.100000",
|
"resolution": null,
|
||||||
"originX": "-111.361710",
|
"originX": null,
|
||||||
"originY": " -255.272873",
|
"originY": null,
|
||||||
"imgList": [
|
"imgList": null,
|
||||||
"http://file.prod.concoai.com/image/4fff5d4bcc4b4239941ff077a0da8958/168c66b32421d8e8d5bc13a0bbb87a30.jpeg"
|
|
||||||
],
|
|
||||||
"robotType": "08",
|
"robotType": "08",
|
||||||
"eventFloor": null,
|
"eventFloor": null,
|
||||||
"floorName": null,
|
"floorName": null,
|
||||||
@ -205,50 +140,124 @@
|
|||||||
"eno": null,
|
"eno": null,
|
||||||
"instrument": null,
|
"instrument": null,
|
||||||
"evideo": null
|
"evideo": null
|
||||||
},
|
}
|
||||||
"rows": null
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
- 获取事件详情接口
|
||||||
|
|
||||||
|
- url:
|
||||||
|
- http://erpapi.concoai.com/robot/event/{eventId}
|
||||||
|
- 传递参数:
|
||||||
|
- 路径变量:
|
||||||
|
- eventId
|
||||||
|
- 请求头参数:
|
||||||
|
- token:
|
||||||
|
- refreshToken
|
||||||
|
- 响应参数: 主要获取imgList中的图片url.
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "2000",
|
||||||
|
"message": "成功",
|
||||||
|
"total": null,
|
||||||
|
"data": {
|
||||||
|
"eventId": "a462a29d7481495380cd47035b19edc0",
|
||||||
|
"tenantInfoId": "4fff5d4bcc4b4239941ff077a0da8958",
|
||||||
|
"reportEventId": "bb96f28e14d944c79f44321edca4395f",
|
||||||
|
"number": "ROB23100098",
|
||||||
|
"name": "X32305000019",
|
||||||
|
"eclassify": "2",
|
||||||
|
"operationType": "2",
|
||||||
|
"etype": "E000007",
|
||||||
|
"etypeName": "日常巡检",
|
||||||
|
"enTypeName": null,
|
||||||
|
"hkTypeName": null,
|
||||||
|
"reportStatus": "0",
|
||||||
|
"results": "2",
|
||||||
|
"insDate": null,
|
||||||
|
"insDateShow": "2025-05-06 16:44:31",
|
||||||
|
"updDate": null,
|
||||||
|
"updDateShow": null,
|
||||||
|
"fileType": "2",
|
||||||
|
"area": "区域1",
|
||||||
|
"floor": "",
|
||||||
|
"map": null,
|
||||||
|
"staffId": null,
|
||||||
|
"targetUserId": null,
|
||||||
|
"position": "75.104378,90.402679",
|
||||||
|
"actualStaffName": null,
|
||||||
|
"targetStaffName": "员工1",
|
||||||
|
"routeName": "test",
|
||||||
|
"phoneAddress": "http://file.prod.concoai.com/image/4fff5d4bcc4b4239941ff077a0da8958/4f60fbd391e98327f6ee13df1455f23e.jpg",
|
||||||
|
"width": "3994.0",
|
||||||
|
"height": "4816.0",
|
||||||
|
"resolution": "0.100000",
|
||||||
|
"originX": "-111.361710",
|
||||||
|
"originY": " -255.272873",
|
||||||
|
"imgList": [
|
||||||
|
"http://file.prod.concoai.com/image/4fff5d4bcc4b4239941ff077a0da8958/168c66b32421d8e8d5bc13a0bbb87a30.jpeg"
|
||||||
|
],
|
||||||
|
"robotType": "08",
|
||||||
|
"eventFloor": null,
|
||||||
|
"floorName": null,
|
||||||
|
"coordId": null,
|
||||||
|
"coord": null,
|
||||||
|
"coordName": null,
|
||||||
|
"positonName": "A3",
|
||||||
|
"processingRemark": null,
|
||||||
|
"carId": null,
|
||||||
|
"parkingSpaceType": null,
|
||||||
|
"parkingSpaceNumber": null,
|
||||||
|
"carNumber": null,
|
||||||
|
"eno": null,
|
||||||
|
"instrument": null,
|
||||||
|
"evideo": null
|
||||||
|
},
|
||||||
|
"rows": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# 后台系统功能:
|
# 后台系统功能:
|
||||||
|
|
||||||
1. WebSocket服务
|
1. WebSocket服务
|
||||||
|
|
||||||
- 实现WebSocket服务器
|
- 实现WebSocket服务器
|
||||||
- 处理WebSocket连接管理
|
- 处理WebSocket连接管理
|
||||||
- 实现消息的接收和响应
|
- 实现消息的接收和响应
|
||||||
- 支持心跳检测和重连机制
|
- 支持心跳检测和重连机制
|
||||||
|
|
||||||
2. 认证管理
|
2. 认证管理
|
||||||
|
|
||||||
- 通过第三方的获取token和refresh接口获取token和refreshToken
|
- 通过第三方的获取token和refresh接口获取token和refreshToken
|
||||||
- 实现token自动刷新机制
|
- 实现token自动刷新机制
|
||||||
- token失效自动重试机制
|
- token失效自动重试机制
|
||||||
|
|
||||||
3. 数据采集
|
3. 数据采集
|
||||||
|
|
||||||
- 定时调用列表页接口获取事件数据
|
- 定时调用列表页接口获取事件数据
|
||||||
- 实现增量采集,避免重复处理
|
- 实现增量采集,避免重复处理
|
||||||
- 异常重试机制
|
- 异常重试机制
|
||||||
- 并发控制
|
- 并发控制
|
||||||
|
|
||||||
4. 数据处理
|
4. 数据处理
|
||||||
|
|
||||||
- 图片下载和预处理
|
- 图片下载和预处理
|
||||||
- OCR识别温度数据
|
- OCR识别温度数据
|
||||||
- 数据清洗和验证
|
- 数据清洗和验证
|
||||||
- 异常数据处理
|
- 异常数据处理
|
||||||
|
|
||||||
5. 数据存储
|
5. 数据存储
|
||||||
|
|
||||||
- 事件信息表
|
- 事件信息表
|
||||||
- 图片信息表
|
- 图片信息表
|
||||||
- 温度数据表
|
- 温度数据表
|
||||||
- 处理日志表
|
- 处理日志表
|
||||||
|
|
||||||
6. API接口
|
6. API接口
|
||||||
|
|
||||||
- WebSocket接口
|
- WebSocket接口
|
||||||
- 温度数据查询接口
|
- 条件获取列表接口
|
||||||
- 处理状态查询接口
|
- 查看列表项详情接口,支持分页查询
|
||||||
- 事件管理接口(增删改查)
|
- 修改列表详情接口
|
||||||
- 手动触发处理接口
|
- 删除列表某一项接口
|
||||||
- 系统状态监控接口
|
|
||||||
|
|
||||||
# 数据库设计:
|
# 数据库设计:
|
||||||
|
|
||||||
1. 事件表(event)
|
1. 事件表(event)
|
||||||
|
|
||||||
- event_id varchar(50) primary key 事件Id
|
- event_id varchar(50) primary key 事件Id
|
||||||
@ -301,8 +310,8 @@
|
|||||||
- evideo varchar(40)
|
- evideo varchar(40)
|
||||||
- create_time datetime COMMENT '本地后台创建时间'
|
- create_time datetime COMMENT '本地后台创建时间'
|
||||||
- update_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT '本地后台更新时间'
|
- update_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT '本地后台更新时间'
|
||||||
|
|
||||||
2. 图片表(image)
|
2. 图片表(image)
|
||||||
|
|
||||||
- image_id: VARCHAR(40) PRIMARY KEY COMMENT '图片ID'
|
- image_id: VARCHAR(40) PRIMARY KEY COMMENT '图片ID'
|
||||||
- event_id: VARCHAR(50) NOT NULL COMMENT '关联事件ID'
|
- event_id: VARCHAR(50) NOT NULL COMMENT '关联事件ID'
|
||||||
- image_url: VARCHAR(500) NOT NULL COMMENT '图片URL'
|
- image_url: VARCHAR(500) NOT NULL COMMENT '图片URL'
|
||||||
@ -310,8 +319,8 @@
|
|||||||
- create_time: DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
|
- create_time: DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
|
||||||
- INDEX idx_event_id (event_id),
|
- INDEX idx_event_id (event_id),
|
||||||
- FOREIGN KEY (event_id) REFERENCES event(event_id)
|
- FOREIGN KEY (event_id) REFERENCES event(event_id)
|
||||||
|
|
||||||
3. 温度数据表(temperature)
|
3. 温度数据表(temperature)
|
||||||
|
|
||||||
- temp_id: VARCHAR(32) PRIMARY KEY COMMENT '温度记录ID'
|
- temp_id: VARCHAR(32) PRIMARY KEY COMMENT '温度记录ID'
|
||||||
- event_id: VARCHAR(50) NOT NULL COMMENT '关联事件ID'
|
- event_id: VARCHAR(50) NOT NULL COMMENT '关联事件ID'
|
||||||
- image_id: VARCHAR(40) NOT NULL COMMENT '关联图片ID'
|
- image_id: VARCHAR(40) NOT NULL COMMENT '关联图片ID'
|
||||||
@ -322,8 +331,8 @@
|
|||||||
- INDEX idx_create_time (create_time),
|
- INDEX idx_create_time (create_time),
|
||||||
- FOREIGN KEY (event_id) REFERENCES event(event_id),
|
- FOREIGN KEY (event_id) REFERENCES event(event_id),
|
||||||
- FOREIGN KEY (image_id) REFERENCES image(image_id)
|
- FOREIGN KEY (image_id) REFERENCES image(image_id)
|
||||||
|
|
||||||
4. 处理日志表(process_log)
|
4. 处理日志表(process_log)
|
||||||
|
|
||||||
- log_id: VARCHAR(32) PRIMARY KEY COMMENT '日志ID'
|
- log_id: VARCHAR(32) PRIMARY KEY COMMENT '日志ID'
|
||||||
- event_id: VARCHAR(32) NOT NULL COMMENT '关联事件ID'
|
- event_id: VARCHAR(32) NOT NULL COMMENT '关联事件ID'
|
||||||
- process_status: TINYINT NOT NULL COMMENT '处理状态'
|
- process_status: TINYINT NOT NULL COMMENT '处理状态'
|
||||||
@ -334,22 +343,21 @@
|
|||||||
- FOREIGN KEY (event_id) REFERENCES event(event_id)
|
- FOREIGN KEY (event_id) REFERENCES event(event_id)
|
||||||
|
|
||||||
# 性能要求:
|
# 性能要求:
|
||||||
|
|
||||||
- 系统响应时间:API接口响应时间<500ms
|
- 系统响应时间:API接口响应时间<500ms
|
||||||
- 并发处理能力:支持10个并发请求
|
- 并发处理能力:支持10个并发请求
|
||||||
- 数据处理能力:每分钟处理不少于100张图片
|
- 数据处理能力:每分钟处理不少于100张图片
|
||||||
- 系统可用性:99.9%
|
- 系统可用性:99.9%
|
||||||
|
|
||||||
# 安全要求:
|
# 安全要求:
|
||||||
|
|
||||||
- 实现接口认证机制
|
- 实现接口认证机制
|
||||||
- 敏感数据加密存储
|
- 敏感数据加密存储
|
||||||
- 操作日志记录
|
- 操作日志记录
|
||||||
- 异常监控告警
|
- 异常监控告警
|
||||||
|
|
||||||
# 部署要求:
|
# 部署要求:
|
||||||
|
|
||||||
- 支持Docker容器化部署
|
- 支持Docker容器化部署
|
||||||
- 支持多环境配置(开发、测试、生产)
|
- 支持多环境配置(开发、测试、生产)
|
||||||
- 提供部署文档和运维手册
|
- 提供部署文档和运维手册
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
27
doc/接口.md
Normal file
27
doc/接口.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
## 获取事件列表
|
||||||
|
|
||||||
|
- GET /api/v1/events?start_time=2024-01-01T00:00:00&end_time=2024-12-31T23:59:59&etype=日常巡检&area=区域1&skip=0&limit=100
|
||||||
|
|
||||||
|
## 获取事件详情
|
||||||
|
|
||||||
|
- GET /api/v1/events/{event_id}
|
||||||
|
|
||||||
|
## 更新事件
|
||||||
|
|
||||||
|
- PUT /api/v1/events/{event_id}
|
||||||
|
Content-Type: application/json
|
||||||
|
- ```json
|
||||||
|
{
|
||||||
|
"name": "新名称",
|
||||||
|
"area": "新区域",
|
||||||
|
"processingRemark": "新备注"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 删除事件
|
||||||
|
|
||||||
|
- DELETE /api/v1/events/{event_id}
|
||||||
|
|
||||||
|
# 运行程序
|
||||||
|
|
||||||
|
- python run.py
|
||||||
52
ocrModel/PP-OCRv4_server_det_inference/inference.yml
Normal file
52
ocrModel/PP-OCRv4_server_det_inference/inference.yml
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
Global:
|
||||||
|
model_name: PP-OCRv4_server_det
|
||||||
|
Hpi:
|
||||||
|
backend_configs:
|
||||||
|
paddle_infer:
|
||||||
|
trt_dynamic_shapes: &id001
|
||||||
|
x:
|
||||||
|
- - 1
|
||||||
|
- 3
|
||||||
|
- 32
|
||||||
|
- 32
|
||||||
|
- - 1
|
||||||
|
- 3
|
||||||
|
- 736
|
||||||
|
- 736
|
||||||
|
- - 1
|
||||||
|
- 3
|
||||||
|
- 4000
|
||||||
|
- 4000
|
||||||
|
tensorrt:
|
||||||
|
dynamic_shapes: *id001
|
||||||
|
PreProcess:
|
||||||
|
transform_ops:
|
||||||
|
- DecodeImage:
|
||||||
|
channel_first: false
|
||||||
|
img_mode: BGR
|
||||||
|
- DetLabelEncode: null
|
||||||
|
- DetResizeForTest: null
|
||||||
|
- NormalizeImage:
|
||||||
|
mean:
|
||||||
|
- 0.485
|
||||||
|
- 0.456
|
||||||
|
- 0.406
|
||||||
|
order: hwc
|
||||||
|
scale: 1./255.
|
||||||
|
std:
|
||||||
|
- 0.229
|
||||||
|
- 0.224
|
||||||
|
- 0.225
|
||||||
|
- ToCHWImage: null
|
||||||
|
- KeepKeys:
|
||||||
|
keep_keys:
|
||||||
|
- image
|
||||||
|
- shape
|
||||||
|
- polys
|
||||||
|
- ignore_tags
|
||||||
|
PostProcess:
|
||||||
|
name: DBPostProcess
|
||||||
|
thresh: 0.3
|
||||||
|
box_thresh: 0.6
|
||||||
|
max_candidates: 1000
|
||||||
|
unclip_ratio: 1.5
|
||||||
6666
ocrModel/rec_ppocr_v4_hgnet_kangda_inference/inference.yml
Normal file
6666
ocrModel/rec_ppocr_v4_hgnet_kangda_inference/inference.yml
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user