feat(auth): 添加用户权限获取接口并完善JWT令牌角色信息

- 在JWT令牌中添加用户角色信息
- 新增get_my_permissions接口用于获取当前用户权限码列表
- 重构认证回调逻辑,增加错误日志记录
- 更新用户信息获取接口使用Authorization头验证
```
This commit is contained in:
2026-04-06 00:40:08 +08:00
parent dfa8fa62a8
commit f1f8518985
71 changed files with 9402 additions and 191 deletions
+163 -12
View File
@@ -5,7 +5,8 @@ from sqlalchemy import asc, desc, distinct, or_
from pydantic import BaseModel
from typing import Optional
from app.core.database import get_db
from app.models.device import ONUDevice, DeviceStatusHistory, OLTDevice
from app.middleware.permission_middleware import require_permission
from app.models.device import ONUDevice, DeviceStatusHistory, OLTDevice, DeviceReplacement
from app.schemas.device import DeviceListResponse, ONUDeviceResponse
router = APIRouter(prefix="/api/devices", tags=["设备管理"])
@@ -18,8 +19,9 @@ def get_devices(
region: str = None,
school_name: str = None,
keyword: str = None,
status: str = None, # online / offline
db: Session = Depends(get_db)
status: str = None,
db: Session = Depends(get_db),
current: dict = Depends(require_permission('device.view')),
):
"""获取设备列表"""
# 子查询:每台设备最新一条状态记录
@@ -44,6 +46,24 @@ def get_devices(
query = db.query(ONUDevice)
# 数据范围过滤:区域管理员只能看自己分配的区域,学校管理员只能看自己分配的学校
role = current.get('role', 'user')
if role == 'area_admin':
assigned = current.get('assigned_area') or ''
areas = [a.strip() for a in assigned.split(',') if a.strip()]
if areas:
query = query.filter(ONUDevice.region.in_(areas))
else:
# 未分配区域则看不到任何设备
query = query.filter(False)
elif role == 'school_admin':
assigned = current.get('assigned_school') or ''
schools = [s.strip() for s in assigned.split(',') if s.strip()]
if schools:
query = query.filter(ONUDevice.school_name.in_(schools))
else:
query = query.filter(False)
if region:
query = query.filter(ONUDevice.region == region)
if school_name:
@@ -141,17 +161,55 @@ def get_devices(
@router.get("/regions")
def get_regions(db: Session = Depends(get_db)):
"""获取所有区域列表"""
regions = db.query(distinct(ONUDevice.region)).filter(
def get_regions(
db: Session = Depends(get_db),
current: dict = Depends(require_permission('device.view')),
):
"""获取所有区域列表(受角色数据范围限制)"""
role = current.get('role', 'user')
query = db.query(distinct(ONUDevice.region)).filter(
ONUDevice.region.isnot(None),
ONUDevice.region != ''
).order_by(ONUDevice.region).all()
return [r[0] for r in regions]
)
if role == 'area_admin':
assigned = current.get('assigned_area') or ''
areas = [a.strip() for a in assigned.split(',') if a.strip()]
if areas:
query = query.filter(ONUDevice.region.in_(areas))
else:
return []
elif role == 'school_admin':
assigned = current.get('assigned_school') or ''
schools = [s.strip() for s in assigned.split(',') if s.strip()]
if schools:
query = query.filter(ONUDevice.school_name.in_(schools))
else:
return []
return [r[0] for r in query.order_by(ONUDevice.region).all()]
@router.get("/schools")
def get_schools(
region: str = None,
db: Session = Depends(get_db),
_: dict = Depends(require_permission('device.view')),
):
"""获取所有学校列表(可按区域筛选)"""
query = db.query(distinct(ONUDevice.school_name)).filter(
ONUDevice.school_name.isnot(None),
ONUDevice.school_name != ''
)
if region:
query = query.filter(ONUDevice.region == region)
return [r[0] for r in query.order_by(ONUDevice.school_name).all()]
@router.get("/{device_id}", response_model=ONUDeviceResponse)
def get_device(device_id: int, db: Session = Depends(get_db)):
def get_device(
device_id: int,
db: Session = Depends(get_db),
_: dict = Depends(require_permission('device.view')),
):
"""获取设备详情"""
device = db.query(ONUDevice).filter(ONUDevice.id == device_id).first()
if not device:
@@ -186,7 +244,11 @@ def get_device(device_id: int, db: Session = Depends(get_db)):
@router.post("/{device_id}/refresh")
def refresh_device_status(device_id: int, db: Session = Depends(get_db)):
def refresh_device_status(
device_id: int,
db: Session = Depends(get_db),
_: dict = Depends(require_permission('device.check')),
):
"""通过 SSH 单独更新一台设备的状态和距离"""
from app.services.check_service import CheckService
try:
@@ -206,8 +268,16 @@ class DeviceUpdate(BaseModel):
notes: Optional[str] = None
class DeviceReplaceRequest(BaseModel):
new_mac: str
reason: Optional[str] = None
@router.delete("/status/all")
def clear_all_status(db: Session = Depends(get_db)):
def clear_all_status(
db: Session = Depends(get_db),
_: dict = Depends(require_permission('device.delete')),
):
"""清空所有设备状态历史记录"""
db.query(DeviceStatusHistory).delete()
db.commit()
@@ -215,7 +285,12 @@ def clear_all_status(db: Session = Depends(get_db)):
@router.put("/{device_id}")
def update_device(device_id: int, body: DeviceUpdate, db: Session = Depends(get_db)):
def update_device(
device_id: int,
body: DeviceUpdate,
db: Session = Depends(get_db),
_: dict = Depends(require_permission('device.edit')),
):
"""更新设备信息(区域、学校、楼宇、房间号、备注)"""
device = db.query(ONUDevice).filter(ONUDevice.id == device_id).first()
if not device:
@@ -229,3 +304,79 @@ def update_device(device_id: int, body: DeviceUpdate, db: Session = Depends(get_
return {"message": "更新成功"}
@router.post("/{device_id}/replace")
def replace_device(
device_id: int,
body: DeviceReplaceRequest,
db: Session = Depends(get_db),
current: dict = Depends(require_permission('device.edit')),
):
"""更换设备 MAC 地址,并记录更换历史"""
from datetime import datetime
device = db.query(ONUDevice).filter(ONUDevice.id == device_id).first()
if not device:
raise HTTPException(status_code=404, detail="设备不存在")
new_mac = body.new_mac.upper().strip()
# 校验 MAC 格式(允许 XX:XX:XX:XX:XX:XX 或 XXXXXXXXXXXX
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 地址格式不正确")
# 检查新 MAC 是否已被其他设备使用
existing = db.query(ONUDevice).filter(
ONUDevice.mac_address == new_mac,
ONUDevice.id != device_id
).first()
if existing:
raise HTTPException(status_code=400, detail="该 MAC 地址已被其他设备使用")
# 同步更新库存序列号设备的 onu_device_id 关联(如有)
from app.models.inventory import SerialDevice
old_serial = db.query(SerialDevice).filter(SerialDevice.onu_device_id == device_id).first()
if old_serial:
old_serial.onu_device_id = None
old_serial.status = "returned"
new_serial = db.query(SerialDevice).filter(SerialDevice.mac_address == new_mac).first()
if new_serial:
new_serial.onu_device_id = device_id
new_serial.status = "in_use"
record = DeviceReplacement(
onu_device_id=device_id,
old_mac=device.mac_address,
new_mac=new_mac,
reason=body.reason or None,
operator_id=current.get("sub", ""),
operator_name=current.get("username", ""),
replaced_at=datetime.utcnow(),
)
db.add(record)
device.mac_address = new_mac
db.commit()
return {"message": "更换成功", "old_mac": record.old_mac, "new_mac": new_mac}
@router.get("/{device_id}/replacements")
def get_device_replacements(
device_id: int,
db: Session = Depends(get_db),
_: dict = Depends(require_permission('device.view')),
):
"""获取设备更换历史"""
records = db.query(DeviceReplacement).filter(
DeviceReplacement.onu_device_id == device_id
).order_by(DeviceReplacement.replaced_at.desc()).all()
return [
{
"id": r.id,
"old_mac": r.old_mac,
"new_mac": r.new_mac,
"reason": r.reason,
"operator_name": r.operator_name,
"replaced_at": r.replaced_at,
}
for r in records
]