Files
iot-device-management-service/app/routers/yudao_auth.py
16337 283f12e443 fix(aiot): 移除实时视频菜单项
从后端菜单定义中彻底删除实时视频菜单条目,
而非仅设置 visible=False,确保前端不再显示该菜单。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 11:17:41 +08:00

346 lines
9.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
芋道认证兼容路由
提供与芋道主平台一致的认证接口,便于前端统一对接。
测试阶段 (dev_mode=True)
- 本地验证 admin/admin
- 返回模拟的 Token 和用户信息
生产阶段 (dev_mode=False)
- 转发到芋道主平台验证
- 返回真实的 Token 和用户信息
API 路径规范:
- /admin-api/system/auth/login - 登录
- /admin-api/system/auth/logout - 登出
- /admin-api/system/auth/refresh-token - 刷新令牌
- /admin-api/system/auth/get-permission-info - 获取权限信息
"""
from fastapi import APIRouter, HTTPException, Header, Query
from pydantic import BaseModel
from typing import Optional
import time
import secrets
from app.config import settings
from app.yudao_compat import YudaoResponse
router = APIRouter(prefix="/admin-api/system", tags=["系统认证"])
class LoginRequest(BaseModel):
"""登录请求"""
username: str
password: str
captchaVerification: Optional[str] = None
# 社交登录参数(预留)
socialType: Optional[int] = None
socialCode: Optional[str] = None
socialState: Optional[str] = None
@router.post("/auth/login")
async def login(req: LoginRequest):
"""
登录接口
测试阶段:验证 admin/admin返回模拟 Token
生产阶段:转发到芋道主平台验证
Returns:
{
"code": 0,
"data": {
"userId": 1,
"accessToken": "xxx",
"refreshToken": "xxx",
"expiresTime": 1234567890000
},
"msg": "success"
}
"""
if settings.app.dev_mode:
# 测试阶段:简单验证 admin/admin
if req.username == "admin" and req.password == "admin":
# 生成模拟 Token
access_token = secrets.token_urlsafe(32)
refresh_token = secrets.token_urlsafe(32)
return YudaoResponse.success({
"userId": 1,
"accessToken": access_token,
"refreshToken": refresh_token,
"expiresTime": int(time.time() * 1000) + 24 * 60 * 60 * 1000 # 24小时后过期
})
raise HTTPException(status_code=401, detail="用户名或密码错误")
# TODO: 生产阶段转发到芋道主平台
# response = requests.post(
# f"{settings.yudao.base_url}/system/auth/login",
# json=req.dict()
# )
# return response.json()
raise HTTPException(status_code=501, detail="生产模式认证未实现,请配置芋道主平台地址")
@router.get("/auth/get-permission-info")
async def get_permission_info(
authorization: Optional[str] = Header(None, alias="Authorization")
):
"""
获取当前用户权限信息
测试阶段:返回超级管理员权限
生产阶段:验证 Token 并获取真实权限
Returns:
{
"code": 0,
"data": {
"user": { "id": 1, "nickname": "超级管理员", ... },
"roles": ["super_admin"],
"permissions": ["*:*:*"]
},
"msg": "success"
}
"""
if settings.app.dev_mode:
return YudaoResponse.success({
"user": {
"id": 1,
"nickname": "超级管理员",
"avatar": "",
"deptId": 1,
"homePath": "/aiot/alarm/list"
},
"roles": ["super_admin"],
"permissions": ["*:*:*"], # 超级权限,拥有所有操作权限
"menus": _build_aiot_menus()
})
# 生产阶段:验证 Token
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="请先登录")
# TODO: 生产阶段调用芋道主平台验证
# token = authorization.replace("Bearer ", "")
# response = requests.get(
# f"{settings.yudao.base_url}/system/auth/get-permission-info",
# headers={"Authorization": f"Bearer {token}"}
# )
# return response.json()
raise HTTPException(status_code=501, detail="生产模式认证未实现")
@router.post("/auth/logout")
async def logout(
authorization: Optional[str] = Header(None, alias="Authorization")
):
"""
登出接口
清除用户登录状态
"""
# 测试阶段和生产阶段都直接返回成功
# 实际的 Token 失效由前端清除 localStorage 完成
return YudaoResponse.success(True)
@router.post("/auth/refresh-token")
async def refresh_token(
refreshToken: str = Query(..., description="刷新令牌")
):
"""
刷新访问令牌
使用 refreshToken 获取新的 accessToken
Returns:
{
"code": 0,
"data": {
"accessToken": "xxx",
"refreshToken": "xxx",
"expiresTime": 1234567890000
},
"msg": "success"
}
"""
if settings.app.dev_mode:
# 测试阶段:直接返回新 Token
new_access_token = secrets.token_urlsafe(32)
new_refresh_token = secrets.token_urlsafe(32)
return YudaoResponse.success({
"accessToken": new_access_token,
"refreshToken": new_refresh_token,
"expiresTime": int(time.time() * 1000) + 24 * 60 * 60 * 1000
})
# TODO: 生产阶段调用芋道主平台刷新
raise HTTPException(status_code=501, detail="生产模式认证未实现")
# ==================== 租户相关接口(预留) ====================
@router.get("/tenant/simple-list")
async def get_tenant_list():
"""
获取租户列表
测试阶段:返回空列表(前端 VITE_APP_TENANT_ENABLE=false 不会调用)
"""
return YudaoResponse.success([])
@router.get("/tenant/get-by-website")
async def get_tenant_by_website(website: str = Query(...)):
"""
根据域名获取租户
测试阶段:返回默认租户
"""
return YudaoResponse.success({
"id": 1,
"name": "默认租户"
})
# ==================== 验证码相关接口(预留) ====================
@router.post("/captcha/get")
async def get_captcha(data: dict):
"""
获取验证码
测试阶段:前端 VITE_APP_CAPTCHA_ENABLE=false 不会调用
"""
return YudaoResponse.success({
"enable": False
})
@router.post("/captcha/check")
async def check_captcha(data: dict):
"""
校验验证码
测试阶段:直接返回成功
"""
return YudaoResponse.success(True)
# ==================== 字典数据接口(预留) ====================
@router.get("/dict-data/simple-list")
async def get_dict_data_simple_list():
"""
获取字典数据列表
测试阶段:返回空列表
"""
return YudaoResponse.success([])
@router.get("/notify-message/get-unread-count")
async def get_unread_message_count():
"""
获取未读消息数量
测试阶段:返回 0
"""
return YudaoResponse.success(0)
# ==================== 辅助函数 ====================
def _build_aiot_menus():
"""
构建 AIoT 菜单树
芋道前端 (accessMode='backend') 期望的菜单格式:
- id, parentId, name, path, component, icon, sort, visible, keepAlive
- 顶层 parentId=0, component='Layout'
- 叶子节点 component 为 views/ 下的路径
"""
return [
{
"id": 2000,
"parentId": 0,
"name": "AIoT 智能平台",
"path": "aiot",
"component": "Layout",
"icon": "ep:monitor",
"sort": 10,
"visible": True,
"keepAlive": True,
"children": [
{
"id": 2010,
"parentId": 2000,
"name": "告警列表",
"path": "alarm/list",
"component": "aiot/alarm/list/index",
"componentName": "AiotAlarmList",
"icon": "ep:warning",
"sort": 1,
"visible": True,
"keepAlive": True,
},
{
"id": 2011,
"parentId": 2000,
"name": "摄像头告警汇总",
"path": "alarm/summary",
"component": "aiot/alarm/summary/index",
"componentName": "AiotAlarmSummary",
"icon": "ep:data-analysis",
"sort": 2,
"visible": True,
"keepAlive": True,
},
{
"id": 2020,
"parentId": 2000,
"name": "摄像头管理",
"path": "device/camera",
"component": "aiot/device/camera/index",
"componentName": "AiotDeviceCamera",
"icon": "ep:camera",
"sort": 3,
"visible": True,
"keepAlive": True,
},
{
"id": 2021,
"parentId": 2000,
"name": "ROI 区域配置",
"path": "device/roi",
"component": "aiot/device/roi/index",
"componentName": "AiotDeviceRoi",
"icon": "ep:aim",
"sort": 4,
"visible": True,
"keepAlive": True,
},
{
"id": 2040,
"parentId": 2000,
"name": "边缘节点管理",
"path": "edge/node",
"component": "aiot/edge/node/index",
"componentName": "AiotEdgeNode",
"icon": "ep:cpu",
"sort": 6,
"visible": True,
"keepAlive": True,
},
],
}
]