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:
2026-04-24 08:53:28 +08:00
parent d222978ae4
commit eaabebbceb
15 changed files with 616 additions and 243 deletions
+49 -7
View File
@@ -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 地址已被其他设备使用")
+24
View File
@@ -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}
+5
View File
@@ -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'},
},
},
)
+16
View File
@@ -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()