feat: v0.8.x 功能更新与数据维护优化
- MAC地址格式统一为小写 xxxx-xxxx-xxxx,支持三种格式输入 - 更换设备MAC时自动清理OLT新发现列表中的冲突ONU记录 - 设备编辑新增场所类型字段(手动输入),区域改为下拉快速填充 - 编辑/更换设备时自动移除new_devices中同MAC的待入库记录 - 新增登录过期自动跳转(401拦截 + 过期提示) - 新增关于页面(/about),支持Markdown渲染,管理员可在设置中编辑内容 - 新增device_status_history每日自动清理任务(保留30天) - 更新README.md和about.md
This commit is contained in:
@@ -265,6 +265,7 @@ class DeviceUpdate(BaseModel):
|
||||
school_name: Optional[str] = None
|
||||
building: Optional[str] = None
|
||||
room_number: Optional[str] = None
|
||||
place_type: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
@@ -299,7 +300,17 @@ def update_device(
|
||||
device.school_name = body.school_name
|
||||
device.building = body.building or None
|
||||
device.room_number = body.room_number or None
|
||||
device.place_type = body.place_type or None
|
||||
device.notes = body.notes or None
|
||||
|
||||
# 若该设备 MAC 在 new_devices 待入库列表中,自动移除(已在设备列表中补全信息)
|
||||
from app.models.device import NewDevice
|
||||
dup_new = db.query(NewDevice).join(
|
||||
ONUDevice, NewDevice.onu_device_id == ONUDevice.id
|
||||
).filter(ONUDevice.mac_address == device.mac_address).all()
|
||||
for nd in dup_new:
|
||||
db.delete(nd)
|
||||
|
||||
db.commit()
|
||||
return {"message": "更新成功"}
|
||||
|
||||
@@ -317,16 +328,47 @@ def replace_device(
|
||||
if not device:
|
||||
raise HTTPException(status_code=404, detail="设备不存在")
|
||||
|
||||
new_mac = body.new_mac.upper().strip()
|
||||
# 校验 MAC 格式(允许 XX:XX:XX:XX:XX:XX 或 XXXXXXXXXXXX)
|
||||
new_mac_raw = body.new_mac.strip()
|
||||
# 接受 xxxx-xxxx-xxxx、xx:xx:xx:xx:xx:xx、xxxxxxxxxxxx 三种格式,统一标准化为小写 xxxx-xxxx-xxxx
|
||||
import re
|
||||
if not re.match(r'^([0-9A-F]{2}[:-]){5}[0-9A-F]{2}$|^[0-9A-F]{12}$', new_mac):
|
||||
raise HTTPException(status_code=400, detail="MAC 地址格式不正确")
|
||||
hex_only = re.sub(r'[:\-]', '', new_mac_raw).lower()
|
||||
if not re.match(r'^[0-9a-f]{12}$', hex_only):
|
||||
raise HTTPException(status_code=400, detail="MAC 地址格式不正确,支持 xxxx-xxxx-xxxx、xx:xx:xx:xx:xx:xx 或 xxxxxxxxxxxx")
|
||||
new_mac = f"{hex_only[0:4]}-{hex_only[4:8]}-{hex_only[8:12]}"
|
||||
|
||||
# 检查新 MAC 是否已被其他设备使用
|
||||
existing = db.query(ONUDevice).filter(
|
||||
ONUDevice.mac_address == new_mac,
|
||||
# 同时匹配大小写和各种分隔符格式,兼容数据库旧数据
|
||||
hex_variants = [
|
||||
new_mac,
|
||||
hex_only,
|
||||
':'.join(hex_only[i:i+2] for i in range(0, 12, 2)),
|
||||
'-'.join(hex_only[i:i+2] for i in range(0, 12, 2)),
|
||||
new_mac.upper(),
|
||||
hex_only.upper(),
|
||||
':'.join(hex_only[i:i+2].upper() for i in range(0, 12, 2)),
|
||||
'-'.join(hex_only[i:i+2].upper() for i in range(0, 12, 2)),
|
||||
]
|
||||
|
||||
# 若新 MAC 在 new_devices 待入库列表中,先删除(更换后该记录已无意义)
|
||||
from app.models.device import NewDevice
|
||||
conflict_news = db.query(NewDevice).join(
|
||||
ONUDevice, NewDevice.onu_device_id == ONUDevice.id
|
||||
).filter(
|
||||
ONUDevice.mac_address.in_(hex_variants),
|
||||
ONUDevice.id != device_id
|
||||
).all()
|
||||
conflict_new_onu_ids = {nd.onu_device_id for nd in conflict_news}
|
||||
for nd in conflict_news:
|
||||
db.delete(nd)
|
||||
# 同时删除对应的空白 ONU 记录,避免设备列表出现重复 MAC
|
||||
if conflict_new_onu_ids:
|
||||
db.query(ONUDevice).filter(ONUDevice.id.in_(conflict_new_onu_ids)).delete(synchronize_session=False)
|
||||
db.flush()
|
||||
|
||||
# 检查新 MAC 是否已被其他 ONU 设备使用(排除刚刚从 new_devices 删除的临时 ONU)
|
||||
existing = db.query(ONUDevice).filter(
|
||||
ONUDevice.mac_address.in_(hex_variants),
|
||||
ONUDevice.id != device_id,
|
||||
ONUDevice.id.notin_(conflict_new_onu_ids) if conflict_new_onu_ids else True,
|
||||
).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="该 MAC 地址已被其他设备使用")
|
||||
|
||||
@@ -74,3 +74,27 @@ def update_check_interval(
|
||||
_get_redis().delete("check_all_devices:last_run")
|
||||
|
||||
return {"key": "check_interval_seconds", "value": seconds}
|
||||
|
||||
|
||||
@router.get("/about")
|
||||
def get_about(db: Session = Depends(get_db)):
|
||||
"""获取关于页面内容(公开接口,无需登录)"""
|
||||
setting = db.query(SystemSetting).filter_by(key='about_content').first()
|
||||
return {"content": setting.value if setting else ""}
|
||||
|
||||
|
||||
@router.put("/about")
|
||||
def update_about(
|
||||
body: dict,
|
||||
db: Session = Depends(get_db),
|
||||
_: dict = Depends(require_permission('*')),
|
||||
):
|
||||
"""更新关于页面内容(仅管理员)"""
|
||||
content = body.get("content", "")
|
||||
setting = db.query(SystemSetting).filter_by(key='about_content').first()
|
||||
if setting:
|
||||
setting.value = content
|
||||
else:
|
||||
db.add(SystemSetting(key='about_content', value=content, description='关于页面内容(Markdown)'))
|
||||
db.commit()
|
||||
return {"key": "about_content", "value": content}
|
||||
|
||||
@@ -34,5 +34,10 @@ celery_app.conf.update(
|
||||
'schedule': crontab(hour=2, minute=0), # 每天凌晨 2:00
|
||||
'options': {'queue': 'h3c_onu_ms'},
|
||||
},
|
||||
'cleanup-status-history': {
|
||||
'task': 'app.tasks.check_tasks.cleanup_status_history',
|
||||
'schedule': crontab(hour=3, minute=0), # 每天凌晨 3:00
|
||||
'options': {'queue': 'h3c_onu_ms'},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -174,3 +174,19 @@ def aggregate_daily_snapshot():
|
||||
return {'success': False, 'error': str(e), 'traceback': traceback.format_exc()}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@celery_app.task
|
||||
def cleanup_status_history():
|
||||
"""清理 30 天前的设备状态历史记录(每天凌晨执行)"""
|
||||
from app.models.device import DeviceStatusHistory
|
||||
db = SessionLocal()
|
||||
try:
|
||||
cutoff = datetime.utcnow() - timedelta(days=30)
|
||||
deleted = db.query(DeviceStatusHistory).filter(
|
||||
DeviceStatusHistory.checked_at < cutoff
|
||||
).delete(synchronize_session=False)
|
||||
db.commit()
|
||||
return {"deleted": deleted}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
Reference in New Issue
Block a user